diff --git a/src/backend/executor/query_dispatch.c b/src/backend/executor/query_dispatch.c index 34e4b84c..c77c5498 100644 --- a/src/backend/executor/query_dispatch.c +++ b/src/backend/executor/query_dispatch.c @@ -2069,8 +2069,10 @@ static bool set_values_need_pipeline(cypher_query *query) cypher_set_item *it = (cypher_set_item *)items->items[i]; if (!it || !it->property || it->property->type == AST_NODE_LABEL_EXPR) continue; if (!set_value_plain(it->expr)) return true; - /* `SET a += {k: a.s}` reads the node it writes */ - if (it->is_merge && it->expr && it->expr->type == AST_NODE_MAP) { + /* `SET a += {k: a.s}` reads the node it writes; `SET a = {k: + * a.s}` too, and bulk SET cleared a before it read the value + * (GQLITE-T-0479). Bulk SET stores only literal map values. */ + if (it->expr && it->expr->type == AST_NODE_MAP) { ast_list *a = ((cypher_map *)it->expr)->pairs; for (int k = 0; a && k < a->count; k++) { ast_node *pv = ((cypher_map_pair *)a->items[k])->value; @@ -5174,6 +5176,8 @@ static bool rw_expr_bound(ast_node *expr, const rw_row *row) } case AST_NODE_NOT_EXPR: return rw_expr_bound(((cypher_not_expr*)expr)->expr, row); case AST_NODE_NULL_CHECK: return rw_expr_bound(((cypher_null_check*)expr)->expr, row); + /* `n:A` in a SET value was left to SET, which refused it (GQLITE-T-0479) */ + case AST_NODE_LABEL_EXPR: return rw_expr_bound(((cypher_label_expr*)expr)->expr, row); case AST_NODE_CASE_EXPR: { cypher_case_expr *c = (cypher_case_expr*)expr; if (!rw_expr_bound(c->operand, row) || !rw_expr_bound(c->else_expr, row)) return false; diff --git a/tests/functional/109_set_map_case.sql b/tests/functional/109_set_map_case.sql new file mode 100644 index 00000000..80ed7e7a --- /dev/null +++ b/tests/functional/109_set_map_case.sql @@ -0,0 +1,31 @@ +-- ======================================================================== +-- Test 109: SET n = map that reads n, and SET values with label tests +-- (GQLITE-T-0479) +-- ======================================================================== +-- PURPOSE: `SET n = {id: n.id}` cleared n and then read n.id, and bulk SET +-- dropped any map value that was not a literal: n lost id. A SET +-- value with a label test (`CASE WHEN n:A ...`, `n:A`) was refused +-- ("SET value must be a literal ..."). +-- NOTE: Assertions are hard: _assert has CHECK (ok = 1), so under +-- `sqlite3 -bail` any failed expectation aborts the run. +-- ======================================================================== + +.load ./build/graphqlite + +SELECT '=== Test 109: SET = map and label tests ===' as test_section; + +CREATE TEMP TABLE _assert(name TEXT, ok INTEGER CHECK (ok = 1)); + +SELECT cypher('CREATE (:A109 {id: 1, v: 5, s: ''x''}), (:B109 {id: 2, v: 6})') as setup; + +SELECT cypher('MATCH (n:A109) SET n = {id: n.id, q: n.v + 1, l: [n.s]}') as w1; +INSERT INTO _assert SELECT 'SET = map reads n before it clears n', + json(cypher('MATCH (n:A109) RETURN properties(n) AS p')) + = json('[{"p":{"id":1,"q":6,"l":["x"]}}]'); + +SELECT cypher('MATCH (n) WHERE n:A109 OR n:B109 SET n.k = CASE WHEN n:A109 THEN ''a'' ELSE ''o'' END, n.t = n:B109') as w2; +INSERT INTO _assert SELECT 'label tests in SET values', + json(cypher('MATCH (n) WHERE n:A109 OR n:B109 RETURN n.id AS id, n.k AS k, n.t AS t ORDER BY id')) + = json('[{"id":1,"k":"a","t":false},{"id":2,"k":"o","t":true}]'); + +SELECT 'Test 109 passed: ' || count(*) || ' assertions' AS result FROM _assert; diff --git a/tests/metamorphic/known.json b/tests/metamorphic/known.json index b55937f5..db9ef285 100644 --- a/tests/metamorphic/known.json +++ b/tests/metamorphic/known.json @@ -1,6 +1,5 @@ [ {"relation": ".", "kind": "error", "error": "invalid JSON result", "task": "GQLITE-T-0484"}, - {"relation": "set_forms/replace", "kind": "mismatch", "task": "GQLITE-T-0479"}, {"relation": "^expr_context/call/", "kind": "error", "error": "is not defined", "task": "GQLITE-T-0487"}, {"relation": "^expr_context/comp/", "kind": "mismatch", "task": "GQLITE-T-0480"}, {"relation": "^expr_context/(unwind|unwind_var|union)/", "kind": "mismatch", "task": "GQLITE-T-0481"},