From f29720daa9b3416d95ea3e59798a781297cff9c6 Mon Sep 17 00:00:00 2001 From: Suhas Narreddy Date: Wed, 21 Oct 2026 18:53:40 +0200 Subject: [PATCH] Cypher: accept every keyword as a map key or a property name --- query/parser/CypherParser.y | 23 +++++ test/query/ir/map/CMakeLists.txt | 3 + test/query/ir/map/KeywordNameTest.cpp | 131 ++++++++++++++++++++++++++ 3 files changed, 157 insertions(+) create mode 100644 test/query/ir/map/KeywordNameTest.cpp diff --git a/query/parser/CypherParser.y b/query/parser/CypherParser.y index 9fcfee459a..93a872a886 100644 --- a/query/parser/CypherParser.y +++ b/query/parser/CypherParser.y @@ -2264,16 +2264,23 @@ dropConstraint reservedWord : PROCEDURES { $$ = Symbol::create(ast, $1); } + | MERGE_DATAPARTS { $$ = Symbol::create(ast, $1); } + | SHORTESTPATH { $$ = Symbol::create(ast, $1); } | DESCENDING { $$ = Symbol::create(ast, $1); } | CONSTRAINT { $$ = Symbol::create(ast, $1); } + | EMBEDDINGS { $$ = Symbol::create(ast, $1); } | MANDATORY { $$ = Symbol::create(ast, $1); } | ASCENDING { $$ = Symbol::create(ast, $1); } + | DIMENSION { $$ = Symbol::create(ast, $1); } | OPTIONAL { $$ = Symbol::create(ast, $1); } | CONTAINS { $$ = Symbol::create(ast, $1); } | DISTINCT { $$ = Symbol::create(ast, $1); } // | EXTRACT { $$ = Symbol::create(ast, $1); } | REQUIRE { $$ = Symbol::create(ast, $1); } | COLLECT { $$ = Symbol::create(ast, $1); } + | CONNECT { $$ = Symbol::create(ast, $1); } + | INDEXES { $$ = Symbol::create(ast, $1); } + | PARQUET { $$ = Symbol::create(ast, $1); } | SUBMIT { $$ = Symbol::create(ast, $1); } | CHANGE { $$ = Symbol::create(ast, $1); } | STARTS { $$ = Symbol::create(ast, $1); } @@ -2289,6 +2296,12 @@ reservedWord | DETACH { $$ = Symbol::create(ast, $1); } | EXISTS { $$ = Symbol::create(ast, $1); } | COMMIT { $$ = Symbol::create(ast, $1); } + | COSINE { $$ = Symbol::create(ast, $1); } + | EUCLID { $$ = Symbol::create(ast, $1); } + | METRIC { $$ = Symbol::create(ast, $1); } + | REDUCE { $$ = Symbol::create(ast, $1); } + | SEARCH { $$ = Symbol::create(ast, $1); } + | VECTOR { $$ = Symbol::create(ast, $1); } | LIMIT { $$ = Symbol::create(ast, $1); } | YIELD { $$ = Symbol::create(ast, $1); } | MATCH { $$ = Symbol::create(ast, $1); } @@ -2302,6 +2315,14 @@ reservedWord | NORMALIZED { $$ = Symbol::create(ast, $1); } | HEADERS { $$ = Symbol::create(ast, $1); } | JSONL { $$ = Symbol::create(ast, $1); } + | GRAPH { $$ = Symbol::create(ast, $1); } + | INDEX { $$ = Symbol::create(ast, $1); } + | FLAT { $$ = Symbol::create(ast, $1); } + | FROM { $$ = Symbol::create(ast, $1); } + | HNSW { $$ = Symbol::create(ast, $1); } + | LOAD { $$ = Symbol::create(ast, $1); } + | PULL { $$ = Symbol::create(ast, $1); } + | PUSH { $$ = Symbol::create(ast, $1); } | CSV { $$ = Symbol::create(ast, $1); } | FAIL { $$ = Symbol::create(ast, $1); } | ERROR_ { $$ = Symbol::create(ast, $1); } @@ -2336,6 +2357,7 @@ reservedWord | FOR { $$ = Symbol::create(ast, $1); } | ADD { $$ = Symbol::create(ast, $1); } | AND { $$ = Symbol::create(ast, $1); } + | GML { $$ = Symbol::create(ast, $1); } | OR { $$ = Symbol::create(ast, $1); } | IN { $$ = Symbol::create(ast, $1); } | IS { $$ = Symbol::create(ast, $1); } @@ -2345,6 +2367,7 @@ reservedWord | ON { $$ = Symbol::create(ast, $1); } | IF { $$ = Symbol::create(ast, $1); } | AS { $$ = Symbol::create(ast, $1); } + | S3 { $$ = Symbol::create(ast, $1); } ; %% diff --git a/test/query/ir/map/CMakeLists.txt b/test/query/ir/map/CMakeLists.txt index e94760759c..9ce6f07db8 100644 --- a/test/query/ir/map/CMakeLists.txt +++ b/test/query/ir/map/CMakeLists.txt @@ -56,3 +56,6 @@ target_include_directories(test_query_ir_dynamic_map_key PRIVATE ${IR_TEST_DIR}) add_turing_test(test_query_ir_unwound_map_key UnwoundMapKeyTest.cpp) target_include_directories(test_query_ir_unwound_map_key PRIVATE ${IR_TEST_DIR}) + +add_turing_test(test_query_ir_keyword_name KeywordNameTest.cpp) +target_include_directories(test_query_ir_keyword_name PRIVATE ${IR_TEST_DIR}) diff --git a/test/query/ir/map/KeywordNameTest.cpp b/test/query/ir/map/KeywordNameTest.cpp new file mode 100644 index 0000000000..75061256b1 --- /dev/null +++ b/test/query/ir/map/KeywordNameTest.cpp @@ -0,0 +1,131 @@ +#include + +#include +#include +#include +#include + +#include "QueryInterpreterV3.h" +#include "QueryStatus.h" + +#include "Graph.h" +#include "QueryConfig.h" +#include "SimpleGraph.h" +#include "SystemAccessor.h" +#include "SystemManager.h" +#include "TuringDB.h" +#include "versioning/ChangeID.h" +#include "versioning/CommitHash.h" + +#include "IRTestRows.h" +#include "TuringTest.h" +#include "TuringTestEnv.h" + +using namespace db; +using namespace turing::test; + +class KeywordNameTest : public TuringTest { +protected: + void initialize() override { + _env = TuringTestEnv::create(fs::Path {_outDir} / "turing"); + _interpreter = std::make_unique(&_env->getSystemManager(), &_env->getMem(), &_env->getCompilerContext()); + + SystemAccessor system = _env->getSystemManager().accessUnique(); + Graph* graph = system.createGraph(_graphName); + SimpleGraph::createSimpleGraph(graph); + } + + void openChange(ChangeID& changeID) { + SystemAccessor system = _env->getSystemManager().accessUnique(); + const auto res = system.newChange(_graphName); + ASSERT_TRUE(res); + + changeID = res.value()->id(); + } + + void submit(const ChangeID& changeID) { + const QueryState submitState(_graphName, + &_env->getMem(), + &_env->getCompilerContext(), + &_queryConfig, + nullptr, + CommitHash::head(), + changeID); + const QueryStatus status = _env->getDB().query("CHANGE SUBMIT", submitState); + ASSERT_TRUE(status.isOk()) << "CHANGE SUBMIT failed"; + } + + void write(std::string_view query) { + ChangeID changeID; + openChange(changeID); + + RowSink sink; + QueryStatus status; + _interpreter->execute(status, + query, + _graphName, + CommitHash::head(), + changeID, + &sink); + ASSERT_TRUE(status.isOk()) << "query: " << query << "\nerror: " << status.getError(); + + submit(changeID); + } + + void expectRows(std::string_view query, const Rows& expected) { + expectRowsIn(ChangeID::head(), query, expected); + } + + void expectRowsIn(const ChangeID& changeID, std::string_view query, const Rows& expected) { + RowSink sink; + QueryStatus status; + _interpreter->execute(status, + query, + _graphName, + CommitHash::head(), + changeID, + &sink); + ASSERT_TRUE(status.isOk()) << "query: " << query << "\nerror: " << status.getError(); + + Rows actual; + sink.sortedRows(actual); + + Rows sortedExpected = expected; + std::sort(sortedExpected.begin(), sortedExpected.end()); + + std::string actualText; + describeRows(actual, actualText); + + EXPECT_EQ(actual, sortedExpected) << "query: " << query << "\ngot:\n" << actualText; + } + + const std::string _graphName = "simpledb"; + std::unique_ptr _env; + std::unique_ptr _interpreter; + QueryConfig _queryConfig; +}; + +TEST_F(KeywordNameTest, comparesMapsKeyedFrom) { + expectRows("UNWIND [1] AS x WITH x WHERE {from: 1} = {from: 1} RETURN count(x)", {{"1"}}); +} + +TEST_F(KeywordNameTest, buildsAMapKeyedFromPerRow) { + expectRows("MATCH (a:Person {name: 'Remy'})-[:KNOWS_WELL]->(b:Person) " + "WITH {from: a.name, to: b.name} AS m WHERE m = {from: 'Remy', to: 'Adam'} RETURN count(*)", + {{"1"}}); +} + +TEST_F(KeywordNameTest, readsKeysNamedAfterKeywords) { + expectRows("WITH {graph: 1, index: 2, search: 3, load: 4, vector: 5, reduce: 6} AS m " + "RETURN m.graph, m.index, m.search, m.load, m.vector, m.reduce", + {{"1", "2", "3", "4", "5", "6"}}); +} + +TEST_F(KeywordNameTest, writesAndReadsPropertiesNamedAfterKeywords) { + write("CREATE (:Tagged {from: 'a', graph: 'b', index: 3})"); + expectRows("MATCH (n:Tagged {from: 'a'}) RETURN n.from, n.graph, n.index", {{"a", "b", "3"}}); +} + +int main(int argc, char** argv) { + return turing::test::turingTestMain(argc, argv); +}