Skip to content

Commit 20a169d

Browse files
authored
Merge pull request #2873 from finos/ndjson
Support ndjson
2 parents 56ee135 + cbadc6d commit 20a169d

24 files changed

Lines changed: 955 additions & 45 deletions

File tree

cpp/perspective/src/cpp/server.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,7 @@ needs_poll(const proto::Request::ClientReqCase proto_case) {
852852
case ReqCase::kViewToColumnsStringReq:
853853
case ReqCase::kViewToCsvReq:
854854
case ReqCase::kViewToRowsStringReq:
855+
case ReqCase::kViewToNdjsonStringReq:
855856
case ReqCase::kViewToArrowReq:
856857
case ReqCase::kViewSchemaReq:
857858
case ReqCase::kViewGetMinMaxReq:
@@ -910,6 +911,7 @@ entity_type_is_table(const proto::Request::ClientReqCase proto_case) {
910911
case ReqCase::kViewDimensionsReq:
911912
case ReqCase::kViewToColumnsStringReq:
912913
case ReqCase::kViewToCsvReq:
914+
case ReqCase::kViewToNdjsonStringReq:
913915
case ReqCase::kViewToRowsStringReq:
914916
case ReqCase::kViewToArrowReq:
915917
case ReqCase::kViewSchemaReq:
@@ -1281,6 +1283,12 @@ ProtoServer::_handle_request(std::uint32_t client_id, const Request& req) {
12811283
Table::from_rows(index, r.data().from_rows(), limit);
12821284
break;
12831285
}
1286+
case proto::MakeTableData::kFromNdjson: {
1287+
table = Table::from_ndjson(
1288+
index, r.data().from_ndjson(), limit
1289+
);
1290+
break;
1291+
}
12841292
case proto::MakeTableData::kFromSchema: {
12851293
std::vector<std::string> columns;
12861294
std::vector<t_dtype> types;
@@ -1489,6 +1497,10 @@ ProtoServer::_handle_request(std::uint32_t client_id, const Request& req) {
14891497
table->update_cols(r.data().from_cols(), r.port_id());
14901498
break;
14911499
}
1500+
case proto::MakeTableData::kFromNdjson: {
1501+
table->update_ndjson(r.data().from_ndjson(), r.port_id());
1502+
break;
1503+
}
14921504
case proto::MakeTableData::kFromSchema:
14931505
case proto::MakeTableData::DATA_NOT_SET:
14941506
default: {
@@ -2038,6 +2050,45 @@ ProtoServer::_handle_request(std::uint32_t client_id, const Request& req) {
20382050
push_resp(std::move(resp));
20392051
break;
20402052
}
2053+
case proto::Request::kViewToNdjsonStringReq: {
2054+
auto view = m_resources.get_view(req.entity_id());
2055+
const auto& r = req.view_to_rows_string_req();
2056+
auto config = view->get_view_config();
2057+
std::string nidx{view_sides_to_string(*view)};
2058+
auto num_hidden = calculate_num_hidden(*view, *config);
2059+
2060+
auto dims = parse_format_options(
2061+
r.viewport(),
2062+
view->num_columns(),
2063+
view->num_rows(),
2064+
view->sides(),
2065+
config->is_column_only(),
2066+
num_hidden
2067+
);
2068+
2069+
auto json_str = view->to_ndjson(
2070+
dims.start_row,
2071+
dims.end_row,
2072+
dims.start_col,
2073+
dims.end_col,
2074+
num_hidden,
2075+
r.formatted(),
2076+
r.index(),
2077+
r.id(),
2078+
r.leaves_only(),
2079+
view->sides(),
2080+
view->sides() > 0 && !config->is_column_only(),
2081+
nidx,
2082+
config->get_columns().size(),
2083+
view->get_view_config()->get_row_pivots().size()
2084+
);
2085+
2086+
proto::Response resp;
2087+
auto* view_cols_str = resp.mutable_view_to_ndjson_string_resp();
2088+
view_cols_str->set_ndjson_string(json_str);
2089+
push_resp(std::move(resp));
2090+
break;
2091+
}
20412092
case proto::Request::kViewToRowsStringReq: {
20422093
auto view = m_resources.get_view(req.entity_id());
20432094
const auto& r = req.view_to_rows_string_req();

cpp/perspective/src/cpp/table.cpp

Lines changed: 254 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,12 +1305,6 @@ Table::from_rows(
13051305
const auto& psp_pkey_col = data_table.get_column("psp_pkey");
13061306
const auto& psp_okey_col = data_table.get_column("psp_okey");
13071307

1308-
// rapidjson::StringBuffer buffer;
1309-
// buffer.Clear();
1310-
// rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
1311-
// document.Accept(writer);
1312-
// std::cout << buffer.GetString() << std::endl;
1313-
13141308
// 3.) Fill table
13151309
for (const auto& row : document.GetArray()) {
13161310
for (const auto& it : row.GetObject()) {
@@ -1352,6 +1346,260 @@ Table::from_rows(
13521346
return tbl;
13531347
}
13541348

1349+
void
1350+
Table::update_ndjson(const std::string_view& data, std::uint32_t port_id) {
1351+
rapidjson::Document document;
1352+
rapidjson::StringStream s(data.data());
1353+
document.ParseStream<rapidjson::kParseStopWhenDoneFlag>(s);
1354+
if (document.Size() == 0) {
1355+
return;
1356+
}
1357+
1358+
if (!document.IsObject()) {
1359+
// TODO Legacy error message
1360+
PSP_COMPLAIN_AND_ABORT(
1361+
"Cannot determine data types without column names!\n"
1362+
)
1363+
}
1364+
1365+
bool is_implicit = m_index.empty();
1366+
t_schema table_schema = get_schema();
1367+
1368+
// 2.) Create table
1369+
t_data_table data_table(table_schema);
1370+
data_table.init();
1371+
1372+
// 2a.) Estimate row size to reduce malloc pressure.
1373+
auto newlines = 0;
1374+
for (char i : data) {
1375+
if (i == '\n') {
1376+
newlines++;
1377+
}
1378+
}
1379+
1380+
data_table.reserve(newlines + 1);
1381+
if (is_implicit) {
1382+
data_table.add_column("psp_pkey", DTYPE_INT32, true);
1383+
} else {
1384+
data_table.add_column(
1385+
"psp_pkey", table_schema.get_dtype(m_index), true
1386+
);
1387+
}
1388+
1389+
t_uindex ii = 0;
1390+
const auto& psp_pkey_col = data_table.get_column("psp_pkey");
1391+
auto schema = data_table.get_schema();
1392+
bool is_first_row = true;
1393+
std::vector<std::string> missing_columns = m_column_names;
1394+
1395+
// 3.) Fill table
1396+
bool is_finished = false;
1397+
while (!is_finished) {
1398+
if (is_implicit) {
1399+
psp_pkey_col->set_nth<std::uint32_t>(ii, (ii + m_offset) % m_limit);
1400+
}
1401+
1402+
for (const auto& it : document.GetObject()) {
1403+
std::shared_ptr<t_column> col;
1404+
std::string_view col_name = it.name.GetString();
1405+
if (std::string_view{it.name.GetString()} == "__INDEX__") {
1406+
col_name = "psp_pkey";
1407+
}
1408+
1409+
if (!schema.has_column(col_name)) {
1410+
LOG_DEBUG("Ignoring column " << col_name);
1411+
LOG_DEBUG("Schema:\n" << schema);
1412+
continue;
1413+
}
1414+
1415+
if (is_first_row) {
1416+
missing_columns.erase(
1417+
std ::remove(
1418+
missing_columns.begin(), missing_columns.end(), col_name
1419+
),
1420+
missing_columns.end()
1421+
);
1422+
}
1423+
1424+
col = data_table.get_column(col_name);
1425+
auto promote = fill_column_json(col, ii, it.value, true);
1426+
if (promote) {
1427+
std::stringstream ss;
1428+
ss << "Cannot append value of type " << dtype_to_str(*promote)
1429+
<< " to column of type " << dtype_to_str(col->get_dtype())
1430+
<< std::endl;
1431+
PSP_COMPLAIN_AND_ABORT(ss.str());
1432+
}
1433+
1434+
if (!is_implicit && m_index == it.name.GetString()) {
1435+
fill_column_json(psp_pkey_col, ii, it.value, true);
1436+
}
1437+
}
1438+
1439+
is_first_row = false;
1440+
if (ii + m_offset >= m_limit) {
1441+
for (auto& col_name : missing_columns) {
1442+
data_table.get_column(col_name)->unset(ii);
1443+
}
1444+
}
1445+
1446+
ii++;
1447+
1448+
document.ParseStream<rapidjson::kParseStopWhenDoneFlag>(s);
1449+
if (document.HasParseError()) {
1450+
is_finished = true;
1451+
}
1452+
}
1453+
1454+
data_table.extend(ii);
1455+
data_table.clone_column("psp_pkey", "psp_okey");
1456+
process_op_column(data_table, t_op::OP_INSERT);
1457+
calculate_offset(ii);
1458+
m_pool->send(get_gnode()->get_id(), port_id, data_table);
1459+
}
1460+
1461+
std::shared_ptr<Table>
1462+
Table::from_ndjson(
1463+
const std::string& index, const std::string_view& data, std::uint32_t limit
1464+
) {
1465+
auto pool = std::make_shared<t_pool>();
1466+
pool->init();
1467+
1468+
// 1.) Infer schema
1469+
rapidjson::Document document;
1470+
rapidjson::StringStream s(data.data());
1471+
document.ParseStream<rapidjson::kParseStopWhenDoneFlag>(s);
1472+
1473+
if (document.Size() > 0 && !document.IsObject()) {
1474+
std::stringstream ss;
1475+
ss << "Received non-object " << document[0].GetType();
1476+
PSP_COMPLAIN_AND_ABORT(ss.str())
1477+
}
1478+
1479+
std::vector<std::string> column_names;
1480+
std::vector<t_dtype> data_types;
1481+
bool is_implicit = true;
1482+
std::set<std::string> columns_known_type;
1483+
std::set<std::string> columns_seen;
1484+
1485+
// TODO I don't think it makes sense to do the same incremental-schema
1486+
// enhancement we do for regular JSON. For now this only checks the first
1487+
// row.
1488+
[&]() {
1489+
for (const auto& col : document.GetObject()) {
1490+
columns_seen.insert(col.name.GetString());
1491+
}
1492+
1493+
// https://github.com/Tencent/rapidjson/issues/1994
1494+
for (const auto& col : document.GetObject()) {
1495+
if (col.name.GetString() == index) {
1496+
is_implicit = false;
1497+
}
1498+
1499+
if (columns_known_type.count(col.name.GetString()) > 0) {
1500+
continue;
1501+
}
1502+
1503+
auto dtype = rapidjson_type_to_dtype(col.value);
1504+
if (dtype != DTYPE_NONE) {
1505+
columns_known_type.insert(col.name.GetString());
1506+
data_types.push_back(rapidjson_type_to_dtype(col.value));
1507+
column_names.emplace_back(col.name.GetString());
1508+
}
1509+
1510+
// Theoretically there can end too early if the first
1511+
// few rows are missing columns that are present in later rows.
1512+
if (columns_known_type.size() == columns_seen.size()) {
1513+
return;
1514+
}
1515+
}
1516+
}();
1517+
1518+
auto untyped_columns = columns_seen;
1519+
for (const auto& col : columns_seen) {
1520+
if (columns_known_type.count(col) == 0) {
1521+
// Default all null columns to string
1522+
data_types.push_back(DTYPE_STR);
1523+
column_names.emplace_back(col);
1524+
}
1525+
}
1526+
1527+
t_schema schema(column_names, data_types);
1528+
1529+
// 2.) Create table
1530+
t_data_table data_table(schema);
1531+
data_table.init();
1532+
1533+
if (is_implicit) {
1534+
data_table.add_column("psp_pkey", DTYPE_INT32, true);
1535+
data_table.add_column("psp_okey", DTYPE_INT32, true);
1536+
} else {
1537+
data_table.add_column("psp_pkey", schema.get_dtype(index), true);
1538+
data_table.add_column("psp_okey", schema.get_dtype(index), true);
1539+
}
1540+
1541+
std::int32_t ii = 0;
1542+
const auto& psp_pkey_col = data_table.get_column("psp_pkey");
1543+
const auto& psp_okey_col = data_table.get_column("psp_okey");
1544+
1545+
// 2a.) Estimate row size to reduce malloc pressure.
1546+
auto newlines = 0;
1547+
for (char i : data) {
1548+
if (i == '\n') {
1549+
newlines++;
1550+
}
1551+
}
1552+
1553+
data_table.reserve(newlines + 1);
1554+
1555+
// 3.) Fill table
1556+
bool is_finished = false;
1557+
while (!is_finished) {
1558+
for (const auto& it : document.GetObject()) {
1559+
auto col = data_table.get_column(it.name.GetString());
1560+
const auto* col_name = it.name.GetString();
1561+
const auto& cell = it.value;
1562+
auto promote = fill_column_json(col, ii, cell, false);
1563+
if (promote) {
1564+
LOG_DEBUG(
1565+
"Promoting column " << col_name << " from "
1566+
<< dtype_to_str(col->get_dtype())
1567+
<< " to " << dtype_to_str(*promote)
1568+
);
1569+
1570+
data_table.promote_column(col_name, *promote, ii, true);
1571+
col = data_table.get_column(col_name);
1572+
fill_column_json(col, ii, cell, false);
1573+
}
1574+
1575+
if (!is_implicit && index == it.name.GetString()) {
1576+
fill_column_json(psp_pkey_col, ii, it.value, false);
1577+
fill_column_json(psp_okey_col, ii, it.value, false);
1578+
}
1579+
}
1580+
1581+
if (is_implicit) {
1582+
psp_pkey_col->set_nth<std::int32_t>(ii, ii % limit);
1583+
psp_okey_col->set_nth<std::int32_t>(ii, ii % limit);
1584+
}
1585+
1586+
ii++;
1587+
document.ParseStream<rapidjson::kParseStopWhenDoneFlag>(s);
1588+
if (document.HasParseError()) {
1589+
is_finished = true;
1590+
}
1591+
}
1592+
1593+
data_table.extend(ii);
1594+
auto tbl = std::make_shared<Table>(
1595+
pool, schema.columns(), schema.types(), limit, index
1596+
);
1597+
1598+
tbl->init(data_table, ii, t_op::OP_INSERT, 0);
1599+
pool->_process();
1600+
return tbl;
1601+
}
1602+
13551603
std::shared_ptr<Table>
13561604
Table::from_schema(
13571605
const std::string& index, const t_schema& schema, std::uint32_t limit

0 commit comments

Comments
 (0)