Repository: kuzudb/kuzu
Pull request #5237: Improve json scan
Author: login: acquamarin
Created: 2025-04-16T05:04:24Z | Merged: 2025-04-17T01:44:41Z

This PR implements the following two optimizations:
1. Remove a regular expression pattern matching which is only used by testing framework
2. Replace case_insensitive map with case_sensitive map which significantly improves the field idx lookup.


Benchmark Data for scanning LDBC-1 comment table:
| Method                    | Time (s)   |
|---------------------------|------------|
| Master                    | 18.726     |
| Remove Regex match       | 2.245      |
| Use case_sensitive map   | 1.658      |



Diff:
diff --git a/extension/json/src/functions/table_functions/json_scan.cpp b/extension/json/src/functions/table_functions/json_scan.cpp
index 8c888452c53..575fb791235 100644
--- a/extension/json/src/functions/table_functions/json_scan.cpp
+++ b/extension/json/src/functions/table_functions/json_scan.cpp
@@ -697,12 +697,13 @@ uint64_t JSONScanLocalState::readNext(
 }
 
 struct JsonScanBindData : public ScanFileBindData {
-    case_insensitive_map_t<idx_t> colNameToIdx;
+    // Note: JSON keys are case-sensitive.
+    std::unordered_map<std::string, idx_t> colNameToIdx;
     JsonScanFormat format;
 
     JsonScanBindData(binder::expression_vector columns, column_id_t numWarningDataColumns,
         FileScanInfo fileScanInfo, main::ClientContext* ctx,
-        case_insensitive_map_t<idx_t> colNameToIdx, JsonScanFormat format)
+        std::unordered_map<std::string, idx_t> colNameToIdx, JsonScanFormat format)
         : ScanFileBindData(columns, 0 /* numRows */, std::move(fileScanInfo), ctx,
               numWarningDataColumns),
           colNameToIdx{std::move(colNameToIdx)}, format{format} {}
@@ -719,23 +720,19 @@ struct JsonScanBindData : public ScanFileBindData {
 };
 
 uint64_t JsonScanBindData::getFieldIdx(const std::string& fieldName) const {
-    auto normalizedName = fieldName;
-    // TODO(Ziyi): this is a temporary fix for the json testing framework, since copy-to-json
-    // always outputs the property name, so we have to remove the property name prefix for
-    // matching.
-    if (!colNameToIdx.contains(fieldName)) {
-        std::regex pattern(R"(^[^.]+\.(.*))");
-        std::smatch match;
-        if (std::regex_match(fieldName, match, pattern) && match.size() > 1) {
-            normalizedName = match[1]; // Return the part of the string after the "xxx."
-        }
+    // From and to are case-insensitive for backward compatibility.
+    if (StringUtils::getLower(fieldName) == "from") {
+        return colNameToIdx.at("from");
+    } else if (StringUtils::getLower(fieldName) == "to") {
+        return colNameToIdx.at("to");
     }
-    return colNameToIdx.contains(normalizedName) ? colNameToIdx.at(normalizedName) : UINT64_MAX;
+    auto itr = colNameToIdx.find(fieldName);
+    return itr == colNameToIdx.end() ? UINT64_MAX : itr->second;
 }
 
 static JsonScanFormat autoDetect(main::ClientContext* context, const std::string& filePath,
     JsonScanConfig& config, std::vector<LogicalType>& types, std::vector<std::string>& names,
-    case_insensitive_map_t<idx_t>& colNameToIdx) {
+    std::unordered_map<std::string, idx_t>& colNameToIdx) {
     auto numRowsToDetect = config.breadth;
     JSONScanSharedState sharedState(*context, filePath, config.format);
     JSONScanLocalState localState(*context->getMemoryManager(), sharedState, context);
@@ -754,13 +751,6 @@ static JsonScanFormat autoDetect(main::ClientContext* context, const std::string
                 ele = yyjson_obj_iter_get_val(key);
                 KU_ASSERT(yyjson_get_type(doc->root) == YYJSON_TYPE_OBJ);
                 std::string fieldName = yyjson_get_str(key);
-                if (!colNameToIdx.contains(fieldName)) {
-                    std::regex pattern(R"(^[^.]+\.(.*))");
-                    std::smatch match;
-                    if (std::regex_match(fieldName, match, pattern) && match.size() > 1) {
-                        fieldName = match[1];
-                    }
-                }
                 idx_t colIdx = 0;
                 if (colNameToIdx.contains(fieldName)) {
                     colIdx = colNameToIdx.at(fieldName);
@@ -789,7 +779,7 @@ static std::unique_ptr<TableFuncBindData> bindFunc(main::ClientContext* context,
     std::vector<LogicalType> columnTypes;
     std::vector<std::string> columnNames;
     JsonScanConfig scanConfig(scanInput->fileScanInfo.options);
-    case_insensitive_map_t<idx_t> colNameToIdx;
+    std::unordered_map<std::string, idx_t> colNameToIdx;
     if (!scanInput->expectedColumnNames.empty() || !scanConfig.autoDetect) {
         if (scanInput->expectedColumnNames.empty()) {
             throw BinderException{
diff --git a/extension/json/test/copy_to_json.test b/extension/json/test/copy_to_json.test
index baf29ac53b8..d5a26e7b2ac 100644
--- a/extension/json/test/copy_to_json.test
+++ b/extension/json/test/copy_to_json.test
@@ -34,7 +34,7 @@ hi
 -CASE CopyToJsonAllTypeTest
 -SKIP_IN_MEM
 -STATEMENT load from '${KUZU_ROOT_DIRECTORY}/dataset/all_types/duckdb_all_type_no_inf.csv' return *
----- 2 
+---- 2
 False|-128|-32768|-2147483648|-9223372036854775808|-170141183460469231731687303715884105728.000000|0|0|0|0|0|5877642-06-25 (BC)|00:00:00|290309-12-22 (BC) 00:00:00|290309-12-22 (BC) 00:00:00|290309-12-22 (BC) 00:00:00|1677-09-22 00:00:00|00:00:00+15:59:59|290309-12-21 (BC) 18:42:28-05:17|-3.4028235e+38|-1.7976931348623157e+308|-999.9|-99999.9999|-1000000000000.0|-1e+28|00000000-0000-0000-0000-000000000000|00:00:00|thisisalongblob\x00withnullbytes|[]|[]|[]|[]|[]|[]|[]|{a: , b: }|{a: , b: }|[]|{}|Frank|[,2,3]|[a,,c]|[[,2,3],,[,2,3]]|[[a,,c],,[a,,c]]|[{a: , b: },{a: 42, b: 🦆🦆🦆🦆🦆🦆},{a: , b: }]|{a: [,2,3], b: [a,,c]}|[[],[42,999,,,-42],[]]|[[,2,3],[4,5,6],[,2,3]]
 True|127|32767|2147483647|9223372036854775807|170141183460469231731687303715884105728.000000|340282366920938463463374607431768211455|255|65535|4294967295|18446744073709551615|5881580-07-10|24:00:00|294247-01-10 04:00:54.775806|294247-01-10 04:00:54|294247-01-10 04:00:54.775|2262-04-11 23:47:16.854775|24:00:00-15:59:59|294247-01-09 23:00:54.776806-05|3.4028235e+38|1.7976931348623157e+308|999.9|99999.9999|1000000000000.0|1e+28|ffffffff-ffff-ffff-ffff-ffffffffffff|83 years 3 months 999 days 00:16:39.999999|\x00\x00\x00a|[42,999,,,-42]|[42.0,,,-42.0]|[1970-01-01,,2022-05-12]|[1970-01-01 00:00:00,,2022-05-12 16:23:45]|[1970-01-01 00:00:00,,2022-05-12 23:23:45]|[🦆🦆🦆🦆🦆🦆,goose,,]|[[],[42,999,,,-42],,[],[42,999,,,-42]]|{a: 42, b: 🦆🦆🦆🦆🦆🦆}|{a: [42,999,,,-42], b: [🦆🦆🦆🦆🦆🦆,goose,,]}|[{a: , b: },{a: 42, b: 🦆🦆🦆🦆🦆🦆},]|{key1=🦆🦆🦆🦆🦆🦆, key2=goose}|5|[4,5,6]|[d,e,f]|[[4,5,6],[,2,3],[4,5,6]]|[[d,e,f],[a,,c],[d,e,f]]|[{a: 42, b: 🦆🦆🦆🦆🦆🦆},{a: , b: },{a: 42, b: 🦆🦆🦆🦆🦆🦆}]|{a: [4,5,6], b: [d,e,f]}|[[42,999,,,-42],[],[42,999,,,-42]]|[[4,5,6],[,2,3],[4,5,6]]
 -STATEMENT COPY (load from '${KUZU_ROOT_DIRECTORY}/dataset/all_types/duckdb_all_type_no_inf.csv' return *) TO "${DATABASE_PATH}/all_types.json"
diff --git a/extension/json/test/example.test b/extension/json/test/example.test
index 04f8911e747..e6f515c0e38 100644
--- a/extension/json/test/example.test
+++ b/extension/json/test/example.test
@@ -30,7 +30,7 @@
 ---- 2
 20|{"height":52,"age":32,"scores":[1,2,5]}
 40|{"age":55,"scores":[1,32,5,null],"name":"dan"}
--STATEMENT COPY (MATCH (p:person) RETURN p.*) To '${KUZU_ROOT_DIRECTORY}/test.json'
+-STATEMENT COPY (MATCH (p:person) with p.id as id, p.description as description return *) To '${KUZU_ROOT_DIRECTORY}/test.json'
 ---- ok
 -STATEMENT DROP TABLE person;
 ---- ok
diff --git a/test/c_api/database_test.cpp b/test/c_api/database_test.cpp
index 69ab520ae9b..1595cfad3bd 100644
--- a/test/c_api/database_test.cpp
+++ b/test/c_api/database_test.cpp
@@ -298,48 +298,3 @@ TEST_F(CApiDatabaseTest, VirtualFileSystemDeleteFilesWildcardNoRemoval) {
     // Cleanup
     std::filesystem::remove_all("/tmp/dbHome_wildcard");
 }
-
-TEST_F(CApiDatabaseTest, dsad) {
-    systemConfig->bufferPoolSize = 1024 * 1024 * 1024;
-    createDBAndConn();
-    printf("%s",
-        conn->query(
-                R"(call NEO4J_MIGRATE("localhost", "neo4j", "czy990424", ["person", "student"], ["knows"]);)")
-            ->toString()
-            .c_str());
-    printf("%s", conn->query(R"(match (p:person) return p.*;)")->toString().c_str());
-    printf("%s", conn->query(R"(match (a)-[e:knows]->(b) return a,e,b;)")->toString().c_str());
-}
-
-TEST_F(CApiDatabaseTest, dsad123) {
-    createDBAndConn();
-    conn->query(
-        "create node table person (ID INt64, fName StRING, gender INT64, isStudent BoOLEAN, "
-        "isWorker BOOLEAN, age INT64, eyeSight DOUBLE, birthdate DATE, registerTime TIMESTAMP, "
-        "lastJobDuration interval, workedHours INT64[], usedNames STRING[], courseScoresPerTerm "
-        "INT64[][], grades INT64[4], height float, u UUID, PRIMARY KEY (ID));");
-    conn->query("copy person from '/Users/z473chen/Desktop/code/kuzu/dataset/tinysnb/vPerson.csv'");
-    conn->query("create rel table knows(from person to person, length int64);");
-    printf("%s", conn->query("copy knows() from (load from "
-                             "'/Users/z473chen/Desktop/code/kuzu/dataset/tinysnb/eKnows.csv' "
-                             "return column0, column1);")
-                     ->toString()
-                     .c_str());
-}
-
-TEST_F(CApiDatabaseTest, dasd) {
-    systemConfig->bufferPoolSize = 1024 * 1024 * 1024;
-    createDBAndConn();
-    printf("%s",
-        conn->query("CALL NEO4J_MIGRATE(\n"
-                    "    \"127.0.0.1\",\n"
-                    "    \"neo4j\",\n"
-                    "    \"czy990424\",\n"
-                    "    [\"Person\", \"Country\", \"City\", \"State\", \"Interest\"],\n"
-                    "    [\"FOLLOWS\", \"LIVES_IN\", \"CITY_IN\", \"STATE_IN\", \"HAS_INTEREST\"]\n"
-                    ");")
-            ->toString()
-            .c_str());
-    printf("%s", conn->query("MATCH (p:person) return p.*")->toString().c_str());
-    printf("%s", conn->query("MATCH ()-[e:FOLLOWS]->() return e")->toString().c_str());
-}
diff --git a/test/include/test_runner/csv_converter.h b/test/include/test_runner/csv_converter.h
index b92c79b3b3f..2beddabe91d 100644
--- a/test/include/test_runner/csv_converter.h
+++ b/test/include/test_runner/csv_converter.h
@@ -33,20 +33,20 @@ class CSVConverter {
         std::string csvFilePath;
         std::string outputFilePath;
         // get cypher query to convert csv file to output file
-        virtual std::string getConverterQuery() const = 0;
+        virtual std::string getConverterQuery(main::ClientContext* context) const = 0;
     };
 
     struct NodeTableInfo final : public TableInfo {
         std::string primaryKey;
         // get converter query for node table
-        std::string getConverterQuery() const override;
+        std::string getConverterQuery(main::ClientContext* context) const override;
     };
 
     struct RelTableInfo final : public TableInfo {
         std::shared_ptr<NodeTableInfo> fromTable;
         std::shared_ptr<NodeTableInfo> toTable;
         // get converter query for rel table
-        std::string getConverterQuery() const override;
+        std::string getConverterQuery(main::ClientContext* context) const override;
     };
 
 private:
diff --git a/test/test_runner/csv_converter.cpp b/test/test_runner/csv_converter.cpp
index d4aea6a4873..aee3be10b72 100644
--- a/test/test_runner/csv_converter.cpp
+++ b/test/test_runner/csv_converter.cpp
@@ -2,6 +2,8 @@
 
 #include <fstream>
 
+#include "catalog/catalog.h"
+#include "catalog/catalog_entry/table_catalog_entry.h"
 #include "common/exception/test.h"
 #include "common/file_system/local_file_system.h"
 #include "common/string_utils.h"
@@ -162,7 +164,7 @@ void CSVConverter::convertCSVFiles() {
     }
     for (auto table : tables) {
         spdlog::info("Converting: {} to {}", table->csvFilePath, table->outputFilePath);
-        auto cmd = table->getConverterQuery();
+        auto cmd = table->getConverterQuery(tempConn->getClientContext());
         auto result = tempConn->query(cmd);
         if (!result->isSuccess()) {
             spdlog::error(result->getErrorMessage());
@@ -193,14 +195,35 @@ void CSVConverter::convertCSVDataset() {
     std::filesystem::remove_all(tempDatabasePath);
 }
 
-std::string CSVConverter::NodeTableInfo::getConverterQuery() const {
-    return stringFormat("COPY (MATCH (a:{}) RETURN a.*) TO \"{}\";", name, outputFilePath);
+static std::string getColumnAlias(main::ClientContext* context, const std::string& tableName) {
+    auto properties =
+        context->getCatalog()
+            ->getTableCatalogEntry(&transaction::DUMMY_CHECKPOINT_TRANSACTION, tableName)
+            ->getProperties();
+    std::string alias;
+    for (auto& property : properties) {
+        if (property.getName() == "_ID") {
+            continue;
+        }
+        alias += common::stringFormat(", e.{} as {}", property.getName(), property.getName());
+    }
+    return alias;
+}
+
+std::string CSVConverter::NodeTableInfo::getConverterQuery(main::ClientContext* context) const {
+    auto alias = getColumnAlias(context, name);
+    if (alias.size() > 1) {
+        alias = alias.substr(1);
+    }
+    return stringFormat("COPY (MATCH (e:{}) WITH {} RETURN *) TO \"{}\";", name, alias,
+        outputFilePath);
 }
 
-std::string CSVConverter::RelTableInfo::getConverterQuery() const {
+std::string CSVConverter::RelTableInfo::getConverterQuery(main::ClientContext* context) const {
+    auto alias = getColumnAlias(context, name);
     return stringFormat(
-        "COPY (MATCH (a)-[e:{}]->(b) RETURN a.{} AS `from`, b.{} AS `to`, e.*) TO \"{}\";", name,
-        fromTable->primaryKey, toTable->primaryKey, outputFilePath);
+        "COPY (MATCH (a)-[e:{}]->(b) WITH a.{} AS `from`, b.{} AS `to`{} RETURN *) TO \"{}\";",
+        name, fromTable->primaryKey, toTable->primaryKey, alias, outputFilePath);
 }
 
 } // namespace testing


Reviews:
user: login: andyfengHKU
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T05:22:09Z
user: login: acquamarin
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-16T05:29:05Z

Review comments:
diff_hunk: @@ -719,23 +720,13 @@ struct JsonScanBindData : public ScanFileBindData {
 };
 
 uint64_t JsonScanBindData::getFieldIdx(const std::string& fieldName) const {
-    auto normalizedName = fieldName;
-    // TODO(Ziyi): this is a temporary fix for the json testing framework, since copy-to-json
-    // always outputs the property name, so we have to remove the property name prefix for
-    // matching.
-    if (!colNameToIdx.contains(fieldName)) {
-        std::regex pattern(R"(^[^.]+\.(.*))");
-        std::smatch match;
-        if (std::regex_match(fieldName, match, pattern) && match.size() > 1) {
-            normalizedName = match[1]; // Return the part of the string after the "xxx."
-        }
-    }
-    return colNameToIdx.contains(normalizedName) ? colNameToIdx.at(normalizedName) : UINT64_MAX;
+    auto itr = colNameToIdx.find(fieldName);
+    return itr == colNameToIdx.end() ? UINT64_MAX : itr->second;
path: extension/json/src/functions/table_functions/json_scan.cpp
user: login: andyfengHKU
body: I thought it's easier to use `colNameToIdx.contains() and .at()` but up to u
created_at: 2025-04-16T05:22:06Z
author_association: CONTRIBUTOR
diff_hunk: @@ -719,23 +720,13 @@ struct JsonScanBindData : public ScanFileBindData {
 };
 
 uint64_t JsonScanBindData::getFieldIdx(const std::string& fieldName) const {
-    auto normalizedName = fieldName;
-    // TODO(Ziyi): this is a temporary fix for the json testing framework, since copy-to-json
-    // always outputs the property name, so we have to remove the property name prefix for
-    // matching.
-    if (!colNameToIdx.contains(fieldName)) {
-        std::regex pattern(R"(^[^.]+\.(.*))");
-        std::smatch match;
-        if (std::regex_match(fieldName, match, pattern) && match.size() > 1) {
-            normalizedName = match[1]; // Return the part of the string after the "xxx."
-        }
-    }
-    return colNameToIdx.contains(normalizedName) ? colNameToIdx.at(normalizedName) : UINT64_MAX;
+    auto itr = colNameToIdx.find(fieldName);
+    return itr == colNameToIdx.end() ? UINT64_MAX : itr->second;
path: extension/json/src/functions/table_functions/json_scan.cpp
user: login: acquamarin
body: The std::unordered_map is much faster than using contains and at.
created_at: 2025-04-16T05:29:05Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-04-16T05:43:20Z
body: # Benchmark Result

Master commit hash: `febb45079741a80c66b88facc4dda407f7aa13e0` 
Branch commit hash: `78d1f482d061656ebed3a8fa4fc7852d1f8674c6` 

| Query Group | Query Name | Mean Time - Commit (ms) | Mean Time - Master (ms) | Diff |
| --- | --- | --- | --- | --- |
| aggregation | q24 | 726.83 | 732.96 | -6.13 (-0.84%) |
| aggregation | q28 | 6588.06 | 6581.92 | 6.14 (0.09%) |
| copy | node-Comment | 78335.70 | N/A | N/A |
| copy | node-Forum | 5937.60 | N/A | N/A |
| copy | node-Organisation | 1290.36 | N/A | N/A |
| copy | node-Person | 2033.39 | N/A | N/A |
| copy | node-Place | 1249.92 | N/A | N/A |
| copy | node-Post | 32124.07 | N/A | N/A |
| copy | node-Tag | 1341.34 | N/A | N/A |
| copy | node-Tagclass | 1224.15 | N/A | N/A |
| copy | rel-comment-hasCreator | 55033.55 | N/A | N/A |
| copy | rel-comment-hasTag | 76202.22 | N/A | N/A |
| copy | rel-comment-isLocatedIn | 63178.69 | N/A | N/A |
| copy | rel-containerOf | 14819.95 | N/A | N/A |
| copy | rel-forum-hasTag | 3668.40 | N/A | N/A |
| copy | rel-hasInterest | 2679.44 | N/A | N/A |
| copy | rel-hasMember | 55041.01 | N/A | N/A |
| copy | rel-hasModerator | 1367.50 | N/A | N/A |
| copy | rel-hasType | 372.07 | N/A | N/A |
| copy | rel-isPartOf | 333.11 | N/A | N/A |
| copy | rel-isSubclassOf | 332.60 | N/A | N/A |
| copy | rel-knows | 6401.57 | N/A | N/A |
| copy | rel-likes-comment | 92084.16 | N/A | N/A |
| copy | rel-likes-post | 34304.93 | N/A | N/A |
| copy | rel-organisation-isLocatedIn | 378.94 | N/A | N/A |
| copy | rel-person-isLocatedIn | 517.16 | N/A | N/A |
| copy | rel-post-hasCreator | 15090.19 | N/A | N/A |
| copy | rel-post-hasTag | 21000.41 | N/A | N/A |
| copy | rel-post-isLocatedIn | 17610.70 | N/A | N/A |
| copy | rel-replyOf-comment | 50887.58 | N/A | N/A |
| copy | rel-replyOf-post | 40317.76 | N/A | N/A |
| copy | rel-studyAt | 578.21 | N/A | N/A |
| copy | rel-workAt | 772.38 | N/A | N/A |
| filter | q14 | 124.57 | 134.12 | -9.55 (-7.12%) |
| filter | q15 | 126.91 | 133.68 | -6.77 (-5.06%) |
| filter | q16 | 339.10 | 351.27 | -12.17 (-3.46%) |
| filter | q17 | 450.52 | 453.74 | -3.22 (-0.71%) |
| filter | q18 | 1923.60 | 1924.03 | -0.43 (-0.02%) |
| filter | zonemap-node | 88.86 | 98.32 | -9.46 (-9.62%) |
| filter | zonemap-node-lhs-cast | 89.92 | 97.35 | -7.43 (-7.63%) |
| filter | zonemap-node-null | 89.91 | 97.21 | -7.30 (-7.51%) |
| filter | zonemap-rel | 5714.51 | 5521.85 | 192.66 (3.49%) |
| fixed_size_expr_evaluator | q07 | 681.58 | 684.06 | -2.48 (-0.36%) |
| fixed_size_expr_evaluator | q08 | 961.65 | 970.20 | -8.54 (-0.88%) |
| fixed_size_expr_evaluator | q09 | 961.56 | 977.00 | -15.44 (-1.58%) |
| fixed_size_expr_evaluator | q10 | 253.37 | 262.60 | -9.23 (-3.51%) |
| fixed_size_expr_evaluator | q11 | 255.36 | 263.38 | -8.02 (-3.04%) |
| fixed_size_expr_evaluator | q12 | 235.40 | 241.91 | -6.51 (-2.69%) |
| fixed_size_expr_evaluator | q13 | 1567.07 | 1563.72 | 3.35 (0.21%) |
| fixed_size_seq_scan | q23 | 109.69 | 119.65 | -9.96 (-8.32%) |
| join | q29 | 761.39 | 729.22 | 32.17 (4.41%) |
| join | q30 | 1750.29 | 1653.85 | 96.45 (5.83%) |
| **join** | **q31** | **7.00** | **5.46** | **1.54 (28.17%)** |
| **join** | **SelectiveTwoHopJoin** | **39.32** | **55.68** | **-16.36 (-29.38%)** |
| ldbc_snb_ic | q35 | 10.42 | 10.32 | 0.10 (0.94%) |
| ldbc_snb_ic | q36 | 93.27 | 97.83 | -4.56 (-4.66%) |
| **ldbc_snb_is** | **q32** | **2.77** | **5.54** | **-2.76 (-49.92%)** |
| ldbc_snb_is | q33 | 11.48 | 12.78 | -1.30 (-10.20%) |
| ldbc_snb_is | q34 | 1.18 | 1.20 | -0.02 (-1.86%) |
| limit | push-down-limit-into-distinct | 2171.23 | 1836.37 | 334.86 (18.24%) |
| multi-rel | multi-rel-large-scan | 1651.33 | 1932.86 | -281.53 (-14.57%) |
| multi-rel | multi-rel-lookup | 12.03 | 11.29 | 0.74 (6.53%) |
| multi-rel | multi-rel-small-scan | 192.69 | 207.74 | -15.05 (-7.24%) |
| order_by | q25 | 131.67 | 135.73 | -4.07 (-3.00%) |
| order_by | q26 | 465.08 | 457.51 | 7.57 (1.65%) |
| order_by | q27 | 1379.02 | 1390.30 | -11.28 (-0.81%) |
| recursive_join | recursive-join-bidirection | 306.67 | 266.97 | 39.70 (14.87%) |
| recursive_join | recursive-join-dense | 7033.59 | 7017.62 | 15.97 (0.23%) |
| recursive_join | recursive-join-path | 23125.17 | 23251.41 | -126.24 (-0.54%) |
| **recursive_join** | **recursive-join-sparse** | **12.06** | **629.52** | **-617.46 (-98.08%)** |
| recursive_join | recursive-join-trail | 7002.09 | 6964.41 | 37.68 (0.54%) |
| scan_after_filter | q01 | 167.97 | 177.86 | -9.88 (-5.56%) |
| scan_after_filter | q02 | 153.02 | 163.63 | -10.61 (-6.48%) |
| shortest_path_ldbc100 | q37 | 84.57 | 91.87 | -7.31 (-7.95%) |
| shortest_path_ldbc100 | q38 | 365.74 | 323.71 | 42.03 (12.98%) |
| **shortest_path_ldbc100** | **q39** | **80.60** | **56.27** | **24.33 (43.24%)** |
| shortest_path_ldbc100 | q40 | 377.06 | 420.09 | -43.04 (-10.24%) |
| var_size_expr_evaluator | q03 | 2096.86 | 2121.58 | -24.72 (-1.16%) |
| var_size_expr_evaluator | q04 | 2242.00 | 2217.58 | 24.42 (1.10%) |
| var_size_expr_evaluator | q05 | 2724.82 | 2605.33 | 119.49 (4.59%) |
| var_size_expr_evaluator | q06 | 1342.66 | 1352.19 | -9.53 (-0.70%) |
| var_size_seq_scan | q19 | 1430.84 | 1434.34 | -3.50 (-0.24%) |
| var_size_seq_scan | q20 | 2636.69 | 2524.95 | 111.73 (4.43%) |
| var_size_seq_scan | q21 | 2287.50 | 2248.58 | 38.91 (1.73%) |
| var_size_seq_scan | q22 | 126.98 | 127.77 | -0.79 (-0.62%) |

author_association: NONE
user: login: codecov[bot]
created_at: 2025-04-16T22:12:34Z
body: ## [Codecov](https://app.codecov.io/gh/kuzudb/kuzu/pull/5237?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb) Report
Attention: Patch coverage is `93.75000%` with `1 line` in your changes missing coverage. Please review.
> Project coverage is 87.00%. Comparing base [(`53ba2e2`)](https://app.codecov.io/gh/kuzudb/kuzu/commit/53ba2e2a222b049116fc398cab98af1ef46ccdf7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb) to head [(`e538448`)](https://app.codecov.io/gh/kuzudb/kuzu/commit/e53844809e7d7a05a68a42857e8ea06e941e3fbf?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb).
> Report is 1 commits behind head on master.

| [Files with missing lines](https://app.codecov.io/gh/kuzudb/kuzu/pull/5237?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb) | Patch % | Lines |
|---|---|---|
| [test/test\_runner/csv\_converter.cpp](https://app.codecov.io/gh/kuzudb/kuzu/pull/5237?src=pr&el=tree&filepath=test%2Ftest_runner%2Fcsv_converter.cpp&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb#diff-dGVzdC90ZXN0X3J1bm5lci9jc3ZfY29udmVydGVyLmNwcA==) | 93.75% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/kuzudb/kuzu/pull/5237?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #5237      +/-   ##
==========================================
- Coverage   87.01%   87.00%   -0.01%     
==========================================
  Files        1413     1413              
  Lines       62231    62219      -12     
  Branches     7620     7619       -1     
==========================================
- Hits        54148    54133      -15     
- Misses       7909     7911       +2     
- Partials      174      175       +1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/kuzudb/kuzu/pull/5237?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kuzudb).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-16T22:17:38Z
body: # Benchmark Result

Master commit hash: `1e2d845c7293a2f5e1ada4963454757d70149e71` 
Branch commit hash: `91fa2e293cf5561191101a30ab705c62440c8996` 

| Query Group | Query Name | Mean Time - Commit (ms) | Mean Time - Master (ms) | Diff |
| --- | --- | --- | --- | --- |
| aggregation | q24 | 725.98 | N/A | N/A |
| aggregation | q28 | 6563.68 | N/A | N/A |
| filter | q14 | 127.44 | N/A | N/A |
| filter | q15 | 129.82 | N/A | N/A |
| filter | q16 | 339.85 | N/A | N/A |
| filter | q17 | 447.82 | N/A | N/A |
| filter | q18 | 1947.21 | N/A | N/A |
| filter | zonemap-node | 90.20 | N/A | N/A |
| filter | zonemap-node-lhs-cast | 91.00 | N/A | N/A |
| filter | zonemap-node-null | 89.91 | N/A | N/A |
| filter | zonemap-rel | 5707.87 | N/A | N/A |
| fixed_size_expr_evaluator | q07 | 678.57 | N/A | N/A |
| fixed_size_expr_evaluator | q08 | 961.87 | N/A | N/A |
| fixed_size_expr_evaluator | q09 | 968.47 | N/A | N/A |
| fixed_size_expr_evaluator | q10 | 256.65 | N/A | N/A |
| fixed_size_expr_evaluator | q11 | 253.89 | N/A | N/A |
| fixed_size_expr_evaluator | q12 | 233.16 | N/A | N/A |
| fixed_size_expr_evaluator | q13 | 1559.24 | N/A | N/A |
| fixed_size_seq_scan | q23 | 113.39 | N/A | N/A |
| join | q29 | 724.40 | N/A | N/A |
| join | q30 | 1548.71 | N/A | N/A |
| join | q31 | 6.59 | N/A | N/A |
| join | SelectiveTwoHopJoin | 49.06 | N/A | N/A |
| ldbc_snb_ic | q35 | 10.05 | N/A | N/A |
| ldbc_snb_ic | q36 | 99.48 | N/A | N/A |
| ldbc_snb_is | q32 | 6.05 | N/A | N/A |
| ldbc_snb_is | q33 | 13.71 | N/A | N/A |
| ldbc_snb_is | q34 | 1.31 | N/A | N/A |
| limit | push-down-limit-into-distinct | 2180.80 | N/A | N/A |
| multi-rel | multi-rel-large-scan | 1677.19 | N/A | N/A |
| multi-rel | multi-rel-lookup | 8.67 | N/A | N/A |
| multi-rel | multi-rel-small-scan | 201.35 | N/A | N/A |
| order_by | q25 | 128.51 | N/A | N/A |
| order_by | q26 | 464.77 | N/A | N/A |
| order_by | q27 | 1380.81 | N/A | N/A |
| recursive_join | recursive-join-bidirection | 300.80 | N/A | N/A |
| recursive_join | recursive-join-dense | 6994.19 | N/A | N/A |
| recursive_join | recursive-join-path | 23483.26 | N/A | N/A |
| recursive_join | recursive-join-sparse | 10.49 | N/A | N/A |
| recursive_join | recursive-join-trail | 6980.18 | N/A | N/A |
| scan_after_filter | q01 | 168.99 | N/A | N/A |
| scan_after_filter | q02 | 156.47 | N/A | N/A |
| shortest_path_ldbc100 | q37 | 87.99 | N/A | N/A |
| shortest_path_ldbc100 | q38 | 405.10 | N/A | N/A |
| shortest_path_ldbc100 | q39 | 86.26 | N/A | N/A |
| shortest_path_ldbc100 | q40 | 395.04 | N/A | N/A |
| var_size_expr_evaluator | q03 | 2082.98 | N/A | N/A |
| var_size_expr_evaluator | q04 | 2216.68 | N/A | N/A |
| var_size_expr_evaluator | q05 | 2727.74 | N/A | N/A |
| var_size_expr_evaluator | q06 | 1341.91 | N/A | N/A |
| var_size_seq_scan | q19 | 1432.85 | N/A | N/A |
| var_size_seq_scan | q20 | 2645.71 | N/A | N/A |
| var_size_seq_scan | q21 | 2296.04 | N/A | N/A |
| var_size_seq_scan | q22 | 126.58 | N/A | N/A |

author_association: NONE