From c223baf85d27ba49838245ab7821ae933ee41982 Mon Sep 17 00:00:00 2001 From: rfdavid Date: Thu, 25 May 2023 21:31:29 -0400 Subject: [PATCH 1/4] Convert demo_db tests to end to end tests --- src/common/string_utils.cpp | 9 + src/include/common/string_utils.h | 3 + test/CMakeLists.txt | 1 - test/demo_db/CMakeLists.txt | 1 - test/demo_db/demo_db_test.cpp | 128 --------- test/include/test_runner/test_parser.h | 4 + test/include/test_runner/test_runner.h | 4 + test/test_files/demo_db/demo_db.test | 9 +- test/test_files/demo_db/demo_db_create.test | 51 ++++ test/test_files/demo_db/demo_db_delete.test | 28 ++ test/test_files/demo_db/demo_db_order.test | 12 +- .../demo_db/demo_db_order_parquet.test | 31 +++ test/test_files/demo_db/demo_db_parquet.test | 260 ++++++++++++++++++ test/test_files/demo_db/demo_db_set_copy.test | 47 ++++ test/test_runner/test_parser.cpp | 11 +- test/test_runner/test_runner.cpp | 68 +++-- 16 files changed, 506 insertions(+), 161 deletions(-) delete mode 100644 test/demo_db/CMakeLists.txt delete mode 100644 test/demo_db/demo_db_test.cpp create mode 100644 test/test_files/demo_db/demo_db_create.test create mode 100644 test/test_files/demo_db/demo_db_delete.test create mode 100644 test/test_files/demo_db/demo_db_order_parquet.test create mode 100644 test/test_files/demo_db/demo_db_parquet.test create mode 100644 test/test_files/demo_db/demo_db_set_copy.test diff --git a/src/common/string_utils.cpp b/src/common/string_utils.cpp index f8aacc8485..a901b0c694 100644 --- a/src/common/string_utils.cpp +++ b/src/common/string_utils.cpp @@ -31,5 +31,14 @@ std::vector StringUtils::splitBySpace(const std::string& input) { return result; } +void StringUtils::replaceAll( + std::string& str, const std::string& search, const std::string& replacement) { + size_t pos = 0; + while ((pos = str.find(search, pos)) != std::string::npos) { + str.replace(pos, search.length(), replacement); + pos += replacement.length(); + } +} + } // namespace common } // namespace kuzu diff --git a/src/include/common/string_utils.h b/src/include/common/string_utils.h index a52afc39c8..4e92dfc33f 100644 --- a/src/include/common/string_utils.h +++ b/src/include/common/string_utils.h @@ -63,6 +63,9 @@ class StringUtils { std::regex whiteSpacePattern{"\\s"}; str = std::regex_replace(str, whiteSpacePattern, ""); } + + static void replaceAll( + std::string& str, const std::string& search, const std::string& replacement); }; } // namespace common diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c349f685fc..d581c190a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,6 @@ add_subdirectory(binder) add_subdirectory(c_api) add_subdirectory(common) add_subdirectory(copy) -add_subdirectory(demo_db) add_subdirectory(main) add_subdirectory(optimizer) add_subdirectory(parser) diff --git a/test/demo_db/CMakeLists.txt b/test/demo_db/CMakeLists.txt deleted file mode 100644 index 8d56c9f9c8..0000000000 --- a/test/demo_db/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_kuzu_test(demo_db_test demo_db_test.cpp) diff --git a/test/demo_db/demo_db_test.cpp b/test/demo_db/demo_db_test.cpp deleted file mode 100644 index ca5186d3ba..0000000000 --- a/test/demo_db/demo_db_test.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include "graph_test/graph_test.h" - -using ::testing::Test; -using namespace kuzu::testing; - -class DemoDBTest : public DBTest { -public: - std::string getInputDir() override { - return TestHelper::appendKuzuRootPath("dataset/demo-db/csv/"); - } -}; - -class DemoDBTestFromParquet : public DBTest { -public: - std::string getInputDir() override { - return TestHelper::appendKuzuRootPath("dataset/demo-db/parquet/"); - } -}; - -TEST_F(DemoDBTest, DemoDBTest) { - runTest(TestHelper::appendKuzuRootPath("test/test_files/demo_db/demo_db.test")); - runTestAndCheckOrder( - TestHelper::appendKuzuRootPath("test/test_files/demo_db/demo_db_order.test")); -} - -TEST_F(DemoDBTestFromParquet, DemoDBTest) { - runTest(TestHelper::appendKuzuRootPath("test/test_files/demo_db/demo_db.test")); - runTestAndCheckOrder( - TestHelper::appendKuzuRootPath("test/test_files/demo_db/demo_db_order.test")); -} - -TEST_F(DemoDBTest, CreateNodeTest1) { - conn->query("create (u:User {name: 'Alice', age: 35})"); - auto result = conn->query("MATCH (a:User) WHERE a.name = 'Alice' RETURN a.name, a.age"); - auto groundTruth = std::vector{"Alice|35"}; - ASSERT_EQ(TestHelper::convertResultToString(*result), groundTruth); -} - -TEST_F(DemoDBTest, CreateNodeTest2) { - conn->query("create (u:User {name: 'Dimitri'})"); - auto result = conn->query("MATCH (a:User) WHERE a.name = 'Dimitri' RETURN a.name, a.age"); - auto groundTruth = std::vector{"Dimitri|"}; - ASSERT_EQ(TestHelper::convertResultToString(*result), groundTruth); -} - -TEST_F(DemoDBTest, CreateRelTest1) { - conn->query("create (u:User {name: 'Alice'})"); - conn->query("MATCH (a:User) WHERE a.name = 'Adam' WITH a MATCH (b:User) WHERE b.name = " - "'Alice' WITH a, b CREATE (a)-[e:Follows {since:1990}]->(b)"); - auto result = conn->query( - "MATCH (a:User)-[:Follows]->(b:User) WHERE a.name = 'Adam' RETURN b.name ORDER BY b.name"); - auto groundTruth = std::vector{"Alice", "Karissa", "Zhang"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); -} - -TEST_F(DemoDBTest, CreateRelTest2) { - conn->query("MATCH (a:User), (b:User) WHERE a.name='Zhang' AND b.name='Karissa' CREATE " - "(a)-[:Follows {since:2022}]->(b)"); - auto result = conn->query( - "MATCH (a:User)-[:Follows]->(b:User) WHERE a.name = 'Zhang' RETURN b.name ORDER BY b.name"); - auto groundTruth = std::vector{"Karissa", "Noura"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); -} - -TEST_F(DemoDBTest, CreateAvgNullTest) { - conn->query( - "MATCH (a:User) WHERE a.name = 'Adam' CREATE (a)-[:Follows]->(b:User {name:'Alice'})"); - auto result = - conn->query("MATCH (a:User) WITH a, avg(a.age) AS b, SUM(a.age) AS c, COUNT(a.age) AS d, " - "COUNT(*) AS e RETURN a, b, c,d, e ORDER BY c DESC"); - auto groundTruth = std::vector{"(label:User, 0:4, {name:Alice, age:})|||0|1", - "(label:User, 0:2, {name:Zhang, age:50})|50.000000|50|1|1", - "(label:User, 0:1, {name:Karissa, age:40})|40.000000|40|1|1", - "(label:User, 0:0, {name:Adam, age:30})|30.000000|30|1|1", - "(label:User, 0:3, {name:Noura, age:25})|25.000000|25|1|1"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); -} - -TEST_F(DemoDBTest, DeleteNodeTest) { - ASSERT_TRUE(conn->query("CREATE (u:User {name: 'Alice', age: 35});")->isSuccess()); - auto result = conn->query("MATCH (u:User) RETURN COUNT(*)"); - auto groundTruth = std::vector{"5"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); - ASSERT_TRUE(conn->query("MATCH (u:User) WHERE u.name = 'Alice' DELETE u;")->isSuccess()); - result = conn->query("MATCH (u:User) RETURN COUNT(*)"); - groundTruth = std::vector{"4"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); -} - -TEST_F(DemoDBTest, DeleteRelTest) { - auto result = conn->query("MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name = 'Adam' AND " - "u1.name = 'Karissa' DELETE f;"); - ASSERT_TRUE(result->isSuccess()); - result = - conn->query("MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name='Adam' RETURN u1.name"); - auto groundTruth = std::vector{"Zhang"}; - ASSERT_EQ(TestHelper::convertResultToString(*result), groundTruth); -} - -TEST_F(DemoDBTest, SetNodeTest) { - ASSERT_TRUE(conn->query("MATCH (u:User) WHERE u.name = 'Adam' SET u.age = 50")->isSuccess()); - auto result = conn->query("MATCH (u:User) WHERE u.name='Adam' RETURN u.age"); - auto groundTruth = std::vector{"50"}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); - ASSERT_TRUE(conn->query("MATCH (u:User) WHERE u.name = 'Adam' SET u.age = NULL")->isSuccess()); - result = conn->query("MATCH (u:User) WHERE u.name='Adam' RETURN u.age"); - groundTruth = std::vector{""}; - ASSERT_EQ(TestHelper::convertResultToString(*result, true /* check order */), groundTruth); -} - -TEST_F(DemoDBTest, SetRelTest) { - auto result = conn->query("MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name = 'Adam' AND " - "u1.name = 'Karissa' SET f.since=2012;"); - ASSERT_TRUE(result->isSuccess()); - result = conn->query( - "MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name='Adam' RETURN f.since, u1.name"); - auto groundTruth = std::vector{"2012|Karissa", "2020|Zhang"}; - ASSERT_EQ(TestHelper::convertResultToString(*result), groundTruth); -} - -TEST_F(DemoDBTest, CopyRelToNonEmptyTableErrorTest) { - ASSERT_TRUE(conn->query("MATCH (:User)-[f:Follows]->(:User) DELETE f")->isSuccess()); - auto result = conn->query("COPY Follows FROM \"" + - TestHelper::appendKuzuRootPath("/dataset/demo-db/csv/follows.csv\"")); - ASSERT_FALSE(result->isSuccess()); - ASSERT_EQ(result->getErrorMessage(), - "Copy exception: COPY commands can only be executed once on a table."); -} diff --git a/test/include/test_runner/test_parser.h b/test/include/test_runner/test_parser.h index 04e4318192..4413132c6d 100644 --- a/test/include/test_runner/test_parser.h +++ b/test/include/test_runner/test_parser.h @@ -39,6 +39,9 @@ const std::unordered_map tokenMap = {{"-GROUP", TokenTyp {"]", TokenType::END_OF_STATEMENT_BLOCK}, {"----", TokenType::RESULT}, {"--", TokenType::SEPARATOR}, {"#", TokenType::EMPTY}}; +const std::unordered_map variableMap = { + {"${KUZU_ROOT_DIRECTORY}", KUZU_ROOT_DIRECTORY}}; + class LogicToken { public: TokenType type; @@ -65,6 +68,7 @@ class TestParser { void extractExpectedResult(TestStatement* currentStatement); void extractStatementBlock(); void addStatementBlock(const std::string& blockName, const std::string& testGroupName); + void replaceVariables(std::string& str); inline bool endOfFile() { return fileStream.eof(); } inline bool nextLine() { return static_cast(getline(fileStream, line)); } inline void checkMinimumParams(int minimumParams) { diff --git a/test/include/test_runner/test_runner.h b/test/include/test_runner/test_runner.h index d4773c35ac..05b805a08a 100644 --- a/test/include/test_runner/test_runner.h +++ b/test/include/test_runner/test_runner.h @@ -21,8 +21,12 @@ class TestRunner { static bool testStatement(TestStatement* statement, main::Connection& conn); static bool checkLogicalPlans(std::unique_ptr& preparedStatement, TestStatement* statement, main::Connection& conn); + static bool checkLogicalPlan(std::unique_ptr& preparedStatement, + TestStatement* statement, uint32_t planIdx, main::Connection& conn); static std::vector convertResultToString( main::QueryResult& queryResult, bool checkOutputOrder = false); + static bool checkPlanResult(std::unique_ptr& result, + TestStatement* statement, const std::string& planStr, uint32_t planIndex); }; } // namespace testing diff --git a/test/test_files/demo_db/demo_db.test b/test/test_files/demo_db/demo_db.test index 47215b6c88..d6bd1db26f 100644 --- a/test/test_files/demo_db/demo_db.test +++ b/test/test_files/demo_db/demo_db.test @@ -1,6 +1,9 @@ -# skip the new framework of executing until we convert this test -# the test will still run through the current implementation --SKIP +-GROUP DemoDBTest +-DATASET demo-db/csv + +-- + +-CASE DemoDBTest -NAME Limit1 -QUERY MATCH (u:User) RETURN u.name ORDER BY u.age DESC LIMIT 3; diff --git a/test/test_files/demo_db/demo_db_create.test b/test/test_files/demo_db/demo_db_create.test new file mode 100644 index 0000000000..dae74e1f79 --- /dev/null +++ b/test/test_files/demo_db/demo_db_create.test @@ -0,0 +1,51 @@ +-GROUP DemoDBCreateTest +-DATASET demo-db/csv + +-- + +-CASE CreateNodeTest1 +-STATEMENT CREATE (u:User {name: 'Alice', age: 35}) +---- ok +-QUERY MATCH (a:User) WHERE a.name = 'Alice' RETURN a.name, a.age +---- 1 +Alice|35 + +-CASE CreateNodeTest2 +-STATEMENT CREATE (u:User {name: 'Dimitri'}) +---- ok +-QUERY MATCH (a:User) WHERE a.name = 'Dimitri' RETURN a.name, a.age +---- 1 +Dimitri| + +-CASE CreateRelTest1 +-STATEMENT CREATE (u:User {name: 'Alice'}) +---- ok +-STATEMENT MATCH (a:User) WHERE a.name = 'Adam' WITH a MATCH (b:User) WHERE b.name = 'Alice' WITH a, b CREATE (a)-[e:Follows {since:1990}]->(b) +---- ok +-CHECK_ORDER +-QUERY MATCH (a:User)-[:Follows]->(b:User) WHERE a.name = 'Adam' RETURN b.name ORDER BY b.name +---- 3 +Alice +Karissa +Zhang + +-CASE CreateRelTest2 +-STATEMENT MATCH (a:User), (b:User) WHERE a.name='Zhang' AND b.name='Karissa' CREATE (a)-[:Follows {since:2022}]->(b) +---- ok +-CHECK_ORDER +-QUERY MATCH (a:User)-[:Follows]->(b:User) WHERE a.name = 'Zhang' RETURN b.name ORDER BY b.name +---- 2 +Karissa +Noura + +-CASE CreateAvgNullTest +-STATEMENT MATCH (a:User) WHERE a.name = 'Adam' CREATE (a)-[:Follows]->(b:User {name:'Alice'}) +---- ok +-CHECK_ORDER +-QUERY MATCH (a:User) WITH a, avg(a.age) AS b, SUM(a.age) AS c, COUNT(a.age) AS d, COUNT(*) AS e RETURN a, b, c,d, e ORDER BY c DESC +---- 5 +(label:User, 0:4, {name:Alice, age:})|||0|1 +(label:User, 0:2, {name:Zhang, age:50})|50.000000|50|1|1 +(label:User, 0:1, {name:Karissa, age:40})|40.000000|40|1|1 +(label:User, 0:0, {name:Adam, age:30})|30.000000|30|1|1 +(label:User, 0:3, {name:Noura, age:25})|25.000000|25|1|1 diff --git a/test/test_files/demo_db/demo_db_delete.test b/test/test_files/demo_db/demo_db_delete.test new file mode 100644 index 0000000000..cb947556ac --- /dev/null +++ b/test/test_files/demo_db/demo_db_delete.test @@ -0,0 +1,28 @@ +-GROUP DemoDBDeleteTest +-DATASET demo-db/csv + +-- + +-CASE DeleteNodeTest +-STATEMENT CREATE (u:User {name: 'Alice', age: 35}) +---- ok +-QUERY MATCH (u:User) RETURN COUNT(*) +---- 1 +5 +-STATEMENT MATCH (u:User) WHERE u.name = 'Alice' DELETE u +---- ok +-QUERY MATCH (u:User) RETURN COUNT(*) +---- 1 +4 + +-CASE DeleteRelTest +-STATEMENT MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name = 'Adam' AND u1.name = 'Karissa' DELETE f +---- ok +-QUERY MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name='Adam' RETURN u1.name +---- 1 +Zhang + +-CASE DeleteWithExceptionTest +-STATEMENT MATCH (u:User) WHERE u.name = 'Adam' DELETE u +---- error +Runtime exception: Currently deleting a node with edges is not supported. node table 0 nodeOffset 0 has 1 (one-to-many or many-to-many) edges for edge file: ${KUZU_ROOT_DIRECTORY}/test/unittest_temp/r-3-0.lists. diff --git a/test/test_files/demo_db/demo_db_order.test b/test/test_files/demo_db/demo_db_order.test index cf869549f3..008da5ef17 100644 --- a/test/test_files/demo_db/demo_db_order.test +++ b/test/test_files/demo_db/demo_db_order.test @@ -1,8 +1,12 @@ -# skip the new framework of executing until we convert this test -# the test will still run through the current implementation --SKIP +-GROUP DemoDBTest +-DATASET demo-db/csv + +-- + +-CASE DemoDBOrderedTest -NAME OrderBy1 +-CHECK_ORDER -QUERY MATCH (u:User) RETURN u.name, u.age ORDER BY u.age; ---- 4 Noura|25 @@ -11,12 +15,14 @@ Karissa|40 Zhang|50 -NAME OrderBy2 +-CHECK_ORDER -QUERY MATCH (u:User)-[:LivesIn]->(c:City) WHERE c.name = "Waterloo" RETURN u.name, u.age ORDER BY u.age DESC; ---- 2 Karissa|40 Adam|30 -NAME OrderBy3 +-CHECK_ORDER -QUERY MATCH (a:User)-[:Follows]->(b:User) RETURN b.age, a.name ORDER BY b.age DESC, a.name DESC; ---- 4 50|Karissa diff --git a/test/test_files/demo_db/demo_db_order_parquet.test b/test/test_files/demo_db/demo_db_order_parquet.test new file mode 100644 index 0000000000..bd6ec027b8 --- /dev/null +++ b/test/test_files/demo_db/demo_db_order_parquet.test @@ -0,0 +1,31 @@ +-GROUP DemoDBTest +-DATASET demo-db/parquet + +-- + +-CASE DemoDBOrderedTestFromParquet + +-NAME OrderBy1 +-CHECK_ORDER +-QUERY MATCH (u:User) RETURN u.name, u.age ORDER BY u.age; +---- 4 +Noura|25 +Adam|30 +Karissa|40 +Zhang|50 + +-NAME OrderBy2 +-CHECK_ORDER +-QUERY MATCH (u:User)-[:LivesIn]->(c:City) WHERE c.name = "Waterloo" RETURN u.name, u.age ORDER BY u.age DESC; +---- 2 +Karissa|40 +Adam|30 + +-NAME OrderBy3 +-CHECK_ORDER +-QUERY MATCH (a:User)-[:Follows]->(b:User) RETURN b.age, a.name ORDER BY b.age DESC, a.name DESC; +---- 4 +50|Karissa +50|Adam +40|Adam +25|Zhang diff --git a/test/test_files/demo_db/demo_db_parquet.test b/test/test_files/demo_db/demo_db_parquet.test new file mode 100644 index 0000000000..1f82459f6e --- /dev/null +++ b/test/test_files/demo_db/demo_db_parquet.test @@ -0,0 +1,260 @@ +-GROUP DemoDBTest +-DATASET demo-db/parquet + +-- + +-CASE DemoDBTestFromParquet + +-NAME Limit1 +-QUERY MATCH (u:User) RETURN u.name ORDER BY u.age DESC LIMIT 3; +---- 3 +Zhang +Karissa +Adam + +-NAME MatchSingleNodeLabel +-QUERY MATCH (a:User) RETURN a; +---- 4 +(label:User, 0:0, {name:Adam, age:30}) +(label:User, 0:1, {name:Karissa, age:40}) +(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:3, {name:Noura, age:25}) + +-NAME MatchMultipleNodeLabels +-QUERY MATCH (a:User:City) RETURN a; +---- 7 +(label:City, 1:0, {name:Waterloo, age:, population:150000}) +(label:City, 1:1, {name:Kitchener, age:, population:200000}) +(label:City, 1:2, {name:Guelph, age:, population:75000}) +(label:User, 0:0, {name:Adam, age:30, population:}) +(label:User, 0:1, {name:Karissa, age:40, population:}) +(label:User, 0:2, {name:Zhang, age:50, population:}) +(label:User, 0:3, {name:Noura, age:25, population:}) + +-NAME MatchAnyNodeLabel +-QUERY MATCH (a) RETURN a; +---- 7 +(label:City, 1:0, {name:Waterloo, age:, population:150000}) +(label:City, 1:1, {name:Kitchener, age:, population:200000}) +(label:City, 1:2, {name:Guelph, age:, population:75000}) +(label:User, 0:0, {name:Adam, age:30, population:}) +(label:User, 0:1, {name:Karissa, age:40, population:}) +(label:User, 0:2, {name:Zhang, age:50, population:}) +(label:User, 0:3, {name:Noura, age:25, population:}) + +-NAME MatchSingleRelLabel +-QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN a.name, e, b.name; +---- 4 +Adam|(0:0)-[label:Follows, {_id:2:0, since:2020}]->(0:1)|Karissa +Adam|(0:0)-[label:Follows, {_id:2:1, since:2020}]->(0:2)|Zhang +Karissa|(0:1)-[label:Follows, {_id:2:2, since:2021}]->(0:2)|Zhang +Zhang|(0:2)-[label:Follows, {_id:2:3, since:2022}]->(0:3)|Noura + +-NAME MatchMultipleRelLabels +-QUERY MATCH (a:User)-[e:Follows|:LivesIn]->(b:User:City) RETURN a.name, e, b.name; +---- 8 +Adam|(0:0)-[label:Follows, {_id:2:0, since:2020}]->(0:1)|Karissa +Adam|(0:0)-[label:Follows, {_id:2:1, since:2020}]->(0:2)|Zhang +Adam|(0:0)-[label:LivesIn, {_id:3:0, since:}]->(1:0)|Waterloo +Karissa|(0:1)-[label:Follows, {_id:2:2, since:2021}]->(0:2)|Zhang +Karissa|(0:1)-[label:LivesIn, {_id:3:1, since:}]->(1:0)|Waterloo +Noura|(0:3)-[label:LivesIn, {_id:3:3, since:}]->(1:2)|Guelph +Zhang|(0:2)-[label:Follows, {_id:2:3, since:2022}]->(0:3)|Noura +Zhang|(0:2)-[label:LivesIn, {_id:3:2, since:}]->(1:1)|Kitchener + +-NAME MatchAnyRelLabel +-QUERY MATCH ()-[e]->() RETURN e; +---- 8 +(0:0)-[label:Follows, {_id:2:0, since:2020}]->(0:1) +(0:0)-[label:Follows, {_id:2:1, since:2020}]->(0:2) +(0:0)-[label:LivesIn, {_id:3:0, since:}]->(1:0) +(0:1)-[label:Follows, {_id:2:2, since:2021}]->(0:2) +(0:1)-[label:LivesIn, {_id:3:1, since:}]->(1:0) +(0:2)-[label:Follows, {_id:2:3, since:2022}]->(0:3) +(0:2)-[label:LivesIn, {_id:3:2, since:}]->(1:1) +(0:3)-[label:LivesIn, {_id:3:3, since:}]->(1:2) + +-NAME MatchTwoHop +-QUERY MATCH (a:User)-[:Follows]->(:User)-[:LivesIn]->(c:City) WHERE a.name = "Adam" RETURN a, c.name, c.population; +---- 2 +(label:User, 0:0, {name:Adam, age:30})|Kitchener|200000 +(label:User, 0:0, {name:Adam, age:30})|Waterloo|150000 + +-NAME MatchCyclic +-QUERY MATCH (a:User)-[:Follows]->(b:User)-[:Follows]->(c:User), (a)-[:Follows]->(c) RETURN a.name, b.name, c.name; +---- 1 +Adam|Karissa|Zhang + +-NAME MatchFilter +-QUERY MATCH (a:User)-[e:Follows {since: 2020}]->(b:User {name: "Zhang"}) RETURN a, e.since, b.name; +---- 1 +(label:User, 0:0, {name:Adam, age:30})|2020|Zhang + +-NAME MatchVarLen +-QUERY MATCH (a:User)-[:Follows*1..2]->(b:User) RETURN a, b; +---- 7 +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:1, {name:Karissa, age:40}) +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:3, {name:Noura, age:25}) +(label:User, 0:1, {name:Karissa, age:40})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:1, {name:Karissa, age:40})|(label:User, 0:3, {name:Noura, age:25}) +(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:3, {name:Noura, age:25}) + +-NAME OptionalMatch1 +-QUERY MATCH (u:User) OPTIONAL MATCH (u)-[:Follows]->(u1:User) RETURN u.name, u1.name; +---- 5 +Adam|Karissa +Adam|Zhang +Karissa|Zhang +Zhang|Noura +Noura| + +-NAME Return1 +-QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN a, e; +---- 4 +(label:User, 0:0, {name:Adam, age:30})|(0:0)-[label:Follows, {_id:2:0, since:2020}]->(0:1) +(label:User, 0:0, {name:Adam, age:30})|(0:0)-[label:Follows, {_id:2:1, since:2020}]->(0:2) +(label:User, 0:1, {name:Karissa, age:40})|(0:1)-[label:Follows, {_id:2:2, since:2021}]->(0:2) +(label:User, 0:2, {name:Zhang, age:50})|(0:2)-[label:Follows, {_id:2:3, since:2022}]->(0:3) + +-NAME Return2 +-QUERY MATCH (a:User)-[:Follows]->(b:User) RETURN *; +---- 4 +(label:User, 0:1, {name:Karissa, age:40})|(label:User, 0:0, {name:Adam, age:30}) +(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:0, {name:Adam, age:30}) +(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:1, {name:Karissa, age:40}) +(label:User, 0:3, {name:Noura, age:25})|(label:User, 0:2, {name:Zhang, age:50}) + +-NAME Return3 +-QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN a.name, a.age, e.since; +---- 4 +Adam|30|2020 +Adam|30|2020 +Karissa|40|2021 +Zhang|50|2022 + +-NAME ReturnDistinct +-QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN DISTINCT a.name, a.age, e.since; +---- 3 +Adam|30|2020 +Karissa|40|2021 +Zhang|50|2022 + +-NAME ReturnGroupByAgg +-QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN a, avg(b.age) as avgFriendAge; +---- 3 +(label:User, 0:0, {name:Adam, age:30})|45.000000 +(label:User, 0:1, {name:Karissa, age:40})|50.000000 +(label:User, 0:2, {name:Zhang, age:50})|25.000000 + +-NAME ReturnGroupByAgg2 +-QUERY MATCH (u:User)-[:LivesIn]->(c:City) RETURN c.name, COUNT(*); +---- 3 +Guelph|1 +Kitchener|1 +Waterloo|2 + +-NAME Skip1 +-QUERY MATCH (u:User) RETURN u.name ORDER BY u.age SKIP 2; +---- 2 +Karissa +Zhang + +-NAME Union1 +-QUERY MATCH (u1:User)-[:LivesIn]->(c1:City) WHERE c1.name = "Waterloo" RETURN u1.name UNION ALL MATCH (u2:User)-[:LivesIn]->(c2:City) WHERE c2.name = "Kitchener" RETURN u2.name; +---- 3 +Karissa +Adam +Zhang + +-NAME Union2 +-QUERY MATCH (u1:User)-[:Follows]->(u2:User) WHERE u2.name = 'Zhang' RETURN u1.age UNION ALL MATCH (u3:User)-[:Follows]->(u4:User) WHERE u4.name = 'Karissa' RETURN u3.age; +---- 3 +30 +40 +30 + +-NAME Union3 +-QUERY MATCH (u1:User)-[:Follows]->(u2:User) WHERE u2.name = 'Zhang' RETURN u1.age UNION MATCH (u3:User)-[:Follows]->(u4:User) WHERE u4.name = 'Karissa' RETURN u3.age; +---- 2 +30 +40 + +-NAME Unwind1 +-QUERY UNWIND ["Amy", "Bob", "Carol"] AS x RETURN 'name' as name, x; +---- 3 +name|Amy +name|Bob +name|Carol + +-NAME Unwind2 +-QUERY UNWIND [["Amy"], ["Bob", "Carol"]] AS x RETURN x; +---- 2 +[Amy] +[Bob,Carol] + +-NAME Where1 +-QUERY MATCH (a:User) WHERE a.age > 45 OR starts_with(a.name, "Kar") RETURN *; +---- 2 +(label:User, 0:1, {name:Karissa, age:40}) +(label:User, 0:2, {name:Zhang, age:50}) + +-NAME Where2 +-QUERY MATCH (a:User) WHERE a.age IS NOT NULL AND starts_with(a.name, "Kar") RETURN *; +---- 1 +(label:User, 0:1, {name:Karissa, age:40}) + +-NAME WhereExists1 +-QUERY MATCH (a:User) WHERE a.age < 100 AND EXISTS { MATCH (a)-[:Follows*3..3]->(b:User)} RETURN a.name, a.age; +---- 1 +Adam|30 + +-NAME WhereExists2 +-QUERY MATCH (a:User) WHERE a.age < 100 AND EXISTS { MATCH (a)-[:Follows*3..3]->(b:User) WHERE EXISTS {MATCH (b)-[:Follows]->(c:User)} } RETURN a.name, a.age; +---- 0 + +-NAME WhereExists3 +-QUERY MATCH (a:User) WHERE a.age < 100 AND EXISTS { MATCH (a)-[:Follows*3..3]->(b:User) WHERE EXISTS {MATCH (b)<-[:Follows]-(c:User)} } RETURN a.name, a.age; +---- 1 +Adam|30 + +-NAME With1 +-QUERY MATCH (a:User) WITH avg(a.age) as avgAge MATCH (b:User) WHERE b.age > avgAge RETURN *; +---- 2 +(label:User, 0:1, {name:Karissa, age:40})|36.250000 +(label:User, 0:2, {name:Zhang, age:50})|36.250000 + +-NAME With2 +-QUERY MATCH (a:User) WITH a ORDER BY a.age DESC LIMIT 1 MATCH (a)-[:Follows]->(b:User) RETURN *; +---- 1 +(label:User, 0:3, {name:Noura, age:25})|(label:User, 0:2, {name:Zhang, age:50}) + +-NAME Undir1 +-QUERY MATCH (a:User)-[:Follows]-(b:User) RETURN a.name, b.age; +---- 8 +Adam|40 +Adam|50 +Karissa|50 +Zhang|25 +Karissa|30 +Zhang|30 +Zhang|40 +Noura|50 + +-NAME Undir2 +-QUERY MATCH (a:User)-[:LivesIn]-(c:City) RETURN a.name, c.name; +---- 8 +Adam|Waterloo +Karissa|Waterloo +Zhang|Kitchener +Noura|Guelph +Waterloo|Karissa +Waterloo|Adam +Kitchener|Zhang +Guelph|Noura + +-NAME Undir3 +-QUERY MATCH ()-[]-() RETURN COUNT(*); +---- 1 +16 diff --git a/test/test_files/demo_db/demo_db_set_copy.test b/test/test_files/demo_db/demo_db_set_copy.test new file mode 100644 index 0000000000..4c3b34ff68 --- /dev/null +++ b/test/test_files/demo_db/demo_db_set_copy.test @@ -0,0 +1,47 @@ +-GROUP DemoDBSetAndCopyTest +-DATASET demo-db/csv + +-- + +-CASE SetNodeTest + +-NAME SetAge +-QUERY MATCH (u:User) WHERE u.name = 'Adam' SET u.age = 50 +---- ok + +-NAME ReturnAge +-QUERY MATCH (u:User) WHERE u.name='Adam' RETURN u.age +---- 1 +50 + +-NAME SetAgeNull +-QUERY MATCH (u:User) WHERE u.name = 'Adam' SET u.age = NULL +---- ok + +-NAME ReturnNullAge +-QUERY MATCH (u:User) WHERE u.name='Adam' RETURN u.age +---- 1 + + +-CASE SetRelTest + +-NAME SetRelSince +-QUERY MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name = 'Adam' AND u1.name = 'Karissa' SET f.since=2012 +---- ok + +-NAME CheckRelSince +-QUERY MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name='Adam' RETURN f.since, u1.name +---- 2 +2012|Karissa +2020|Zhang + +-NAME CopyRelToNonEmptyTableErrorTest +-STATEMENT MATCH (:User)-[f:Follows]->(:User) DELETE f +---- ok + + +-CASE CopyRelToNonEmptyTableErrorTest + +-QUERY COPY Follows FROM "${KUZU_ROOT_DIRECTORY}/dataset/demo-db/csv/follows.csv" +---- error +Copy exception: COPY commands can only be executed once on a table. diff --git a/test/test_runner/test_parser.cpp b/test/test_runner/test_parser.cpp index e4dfcea6c0..8849a26e9a 100644 --- a/test/test_runner/test_parser.cpp +++ b/test/test_runner/test_parser.cpp @@ -54,6 +54,12 @@ void TestParser::parseHeader() { } } +void TestParser::replaceVariables(std::string& str) { + for (auto& variable : variableMap) { + StringUtils::replaceAll(str, variable.first, variable.second); + } +} + void TestParser::extractExpectedResult(TestStatement* statement) { checkMinimumParams(1); std::string result = currentToken.params[1]; @@ -62,6 +68,7 @@ void TestParser::extractExpectedResult(TestStatement* statement) { } else if (result == "error") { statement->expectedError = true; statement->errorMessage = extractTextBeforeNextStatement(); + replaceVariables(statement->errorMessage); } else { statement->expectedNumTuples = stoi(result); for (auto i = 0u; i < statement->expectedNumTuples; i++) { @@ -103,7 +110,9 @@ TestStatement* TestParser::extractStatement(TestStatement* statement) { case TokenType::STATEMENT: case TokenType::QUERY: { checkMinimumParams(1); - statement->query = paramsToString(); + std::string query = paramsToString(); + replaceVariables(query); + statement->query = query; break; } case TokenType::RESULT: { diff --git a/test/test_runner/test_runner.cpp b/test/test_runner/test_runner.cpp index 19dbad1038..dd7fb4d29f 100644 --- a/test/test_runner/test_runner.cpp +++ b/test/test_runner/test_runner.cpp @@ -30,14 +30,8 @@ bool TestRunner::testStatement(TestStatement* statement, Connection& conn) { } else { preparedStatement = conn.prepareNoLock(statement->query, true, statement->encodedJoin); } - if (statement->expectedOk) { - return preparedStatement->isSuccess(); - } - if (statement->expectedError) { - return ( - statement->errorMessage == StringUtils::rtrim(preparedStatement->getErrorMessage())); - } - if (!preparedStatement->isSuccess()) { + // Check for wrong statements + if (!statement->expectedError && !preparedStatement->isSuccess()) { spdlog::error(preparedStatement->getErrorMessage()); return false; } @@ -48,29 +42,55 @@ bool TestRunner::checkLogicalPlans(std::unique_ptr& preparedS TestStatement* statement, Connection& conn) { auto numPlans = preparedStatement->logicalPlans.size(); auto numPassedPlans = 0u; + if (numPlans == 0) { + return checkLogicalPlan(preparedStatement, statement, 0, conn); + } for (auto i = 0u; i < numPlans; ++i) { - auto planStr = preparedStatement->logicalPlans[i]->toString(); - auto result = conn.executeAndAutoCommitIfNecessaryNoLock(preparedStatement.get(), i); - assert(result->isSuccess()); - std::vector resultTuples = - TestRunner::convertResultToString(*result, statement->checkOutputOrder); - if (resultTuples.size() == result->getNumTuples() && - resultTuples == statement->expectedTuples) { - spdlog::info( - "PLAN{} PASSED in {}ms.", i, result->getQuerySummary()->getExecutionTime()); + if (checkLogicalPlan(preparedStatement, statement, i, conn)) { numPassedPlans++; - } else { - spdlog::error("PLAN{} NOT PASSED.", i); - spdlog::info("PLAN: \n{}", planStr); - spdlog::info("RESULT: \n"); - for (auto& tuple : resultTuples) { - spdlog::info(tuple); - } } } return numPassedPlans == numPlans; } +bool TestRunner::checkLogicalPlan(std::unique_ptr& preparedStatement, + TestStatement* statement, uint32_t planIdx, Connection& conn) { + auto result = conn.executeAndAutoCommitIfNecessaryNoLock(preparedStatement.get(), planIdx); + if (statement->expectedError) { + if (statement->errorMessage == StringUtils::rtrim(result->getErrorMessage())) { + return true; + } + } else if (statement->expectedOk && result->isSuccess()) { + return true; + } else { + auto planStr = preparedStatement->logicalPlans[planIdx]->toString(); + if (checkPlanResult(result, statement, planStr, planIdx)) { + return true; + } + } + return false; +} + +bool TestRunner::checkPlanResult(std::unique_ptr& result, TestStatement* statement, + const std::string& planStr, uint32_t planIdx) { + std::vector resultTuples = + TestRunner::convertResultToString(*result, statement->checkOutputOrder); + if (resultTuples.size() == result->getNumTuples() && + resultTuples == statement->expectedTuples) { + spdlog::info( + "PLAN{} PASSED in {}ms.", planIdx, result->getQuerySummary()->getExecutionTime()); + return true; + } else { + spdlog::error("PLAN{} NOT PASSED.", planIdx); + spdlog::info("PLAN: \n{}", planStr); + spdlog::info("RESULT: \n"); + for (auto& tuple : resultTuples) { + spdlog::info(tuple); + } + } + return false; +} + std::vector TestRunner::convertResultToString( QueryResult& queryResult, bool checkOutputOrder) { std::vector actualOutput; From 12fc685c731831c1266bafdee52f2e0879af2a80 Mon Sep 17 00:00:00 2001 From: rfdavid Date: Fri, 26 May 2023 13:16:59 -0400 Subject: [PATCH 2/4] checkLogicalPlan parameter adjustment --- test/include/test_runner/test_runner.h | 2 +- test/test_runner/test_runner.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/include/test_runner/test_runner.h b/test/include/test_runner/test_runner.h index 05b805a08a..f16e42764a 100644 --- a/test/include/test_runner/test_runner.h +++ b/test/include/test_runner/test_runner.h @@ -22,7 +22,7 @@ class TestRunner { static bool checkLogicalPlans(std::unique_ptr& preparedStatement, TestStatement* statement, main::Connection& conn); static bool checkLogicalPlan(std::unique_ptr& preparedStatement, - TestStatement* statement, uint32_t planIdx, main::Connection& conn); + TestStatement* statement, main::Connection& conn, uint32_t planIdx); static std::vector convertResultToString( main::QueryResult& queryResult, bool checkOutputOrder = false); static bool checkPlanResult(std::unique_ptr& result, diff --git a/test/test_runner/test_runner.cpp b/test/test_runner/test_runner.cpp index dd7fb4d29f..9f38d99cc4 100644 --- a/test/test_runner/test_runner.cpp +++ b/test/test_runner/test_runner.cpp @@ -43,10 +43,10 @@ bool TestRunner::checkLogicalPlans(std::unique_ptr& preparedS auto numPlans = preparedStatement->logicalPlans.size(); auto numPassedPlans = 0u; if (numPlans == 0) { - return checkLogicalPlan(preparedStatement, statement, 0, conn); + return checkLogicalPlan(preparedStatement, statement, conn, 0); } for (auto i = 0u; i < numPlans; ++i) { - if (checkLogicalPlan(preparedStatement, statement, i, conn)) { + if (checkLogicalPlan(preparedStatement, statement, conn, i)) { numPassedPlans++; } } @@ -54,7 +54,7 @@ bool TestRunner::checkLogicalPlans(std::unique_ptr& preparedS } bool TestRunner::checkLogicalPlan(std::unique_ptr& preparedStatement, - TestStatement* statement, uint32_t planIdx, Connection& conn) { + TestStatement* statement, Connection& conn, uint32_t planIdx) { auto result = conn.executeAndAutoCommitIfNecessaryNoLock(preparedStatement.get(), planIdx); if (statement->expectedError) { if (statement->errorMessage == StringUtils::rtrim(result->getErrorMessage())) { From 9bed96cd10bde0016f889616c5b1fd52a9f5891e Mon Sep 17 00:00:00 2001 From: rfdavid Date: Sat, 27 May 2023 07:19:53 -0400 Subject: [PATCH 3/4] Rebase and remove DeleteWithExceptionTest test --- test/test_files/demo_db/demo_db_delete.test | 5 ----- test/test_files/demo_db/demo_db_set_copy.test | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/test/test_files/demo_db/demo_db_delete.test b/test/test_files/demo_db/demo_db_delete.test index cb947556ac..41d8d16812 100644 --- a/test/test_files/demo_db/demo_db_delete.test +++ b/test/test_files/demo_db/demo_db_delete.test @@ -21,8 +21,3 @@ -QUERY MATCH (u:User)-[f:Follows]->(u1:User) WHERE u.name='Adam' RETURN u1.name ---- 1 Zhang - --CASE DeleteWithExceptionTest --STATEMENT MATCH (u:User) WHERE u.name = 'Adam' DELETE u ----- error -Runtime exception: Currently deleting a node with edges is not supported. node table 0 nodeOffset 0 has 1 (one-to-many or many-to-many) edges for edge file: ${KUZU_ROOT_DIRECTORY}/test/unittest_temp/r-3-0.lists. diff --git a/test/test_files/demo_db/demo_db_set_copy.test b/test/test_files/demo_db/demo_db_set_copy.test index 4c3b34ff68..f51b3b2fa2 100644 --- a/test/test_files/demo_db/demo_db_set_copy.test +++ b/test/test_files/demo_db/demo_db_set_copy.test @@ -42,6 +42,6 @@ -CASE CopyRelToNonEmptyTableErrorTest --QUERY COPY Follows FROM "${KUZU_ROOT_DIRECTORY}/dataset/demo-db/csv/follows.csv" +-STATEMENT COPY Follows FROM "${KUZU_ROOT_DIRECTORY}/dataset/demo-db/csv/follows.csv" ---- error Copy exception: COPY commands can only be executed once on a table. From 47426ad218a693eb8131d9ad330cf9447991367c Mon Sep 17 00:00:00 2001 From: rfdavid Date: Sat, 27 May 2023 16:51:04 -0400 Subject: [PATCH 4/4] Fix demodb parquet test --- test/test_files/demo_db/demo_db_parquet.test | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/test_files/demo_db/demo_db_parquet.test b/test/test_files/demo_db/demo_db_parquet.test index 1f82459f6e..78b37cfeaf 100644 --- a/test/test_files/demo_db/demo_db_parquet.test +++ b/test/test_files/demo_db/demo_db_parquet.test @@ -121,10 +121,10 @@ Noura| -NAME Return2 -QUERY MATCH (a:User)-[:Follows]->(b:User) RETURN *; ---- 4 -(label:User, 0:1, {name:Karissa, age:40})|(label:User, 0:0, {name:Adam, age:30}) -(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:0, {name:Adam, age:30}) -(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:1, {name:Karissa, age:40}) -(label:User, 0:3, {name:Noura, age:25})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:1, {name:Karissa, age:40}) +(label:User, 0:0, {name:Adam, age:30})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:1, {name:Karissa, age:40})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:3, {name:Noura, age:25}) -NAME Return3 -QUERY MATCH (a:User)-[e:Follows]->(b:User) RETURN a.name, a.age, e.since; @@ -222,13 +222,13 @@ Adam|30 -NAME With1 -QUERY MATCH (a:User) WITH avg(a.age) as avgAge MATCH (b:User) WHERE b.age > avgAge RETURN *; ---- 2 -(label:User, 0:1, {name:Karissa, age:40})|36.250000 -(label:User, 0:2, {name:Zhang, age:50})|36.250000 +36.250000|(label:User, 0:1, {name:Karissa, age:40}) +36.250000|(label:User, 0:2, {name:Zhang, age:50}) -NAME With2 -QUERY MATCH (a:User) WITH a ORDER BY a.age DESC LIMIT 1 MATCH (a)-[:Follows]->(b:User) RETURN *; ---- 1 -(label:User, 0:3, {name:Noura, age:25})|(label:User, 0:2, {name:Zhang, age:50}) +(label:User, 0:2, {name:Zhang, age:50})|(label:User, 0:3, {name:Noura, age:25}) -NAME Undir1 -QUERY MATCH (a:User)-[:Follows]-(b:User) RETURN a.name, b.age;