-
Notifications
You must be signed in to change notification settings - Fork 481
Add MERGE ON CREATE SET / ON MATCH SET support (#1619) #2347
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
0fd8992
40dddbd
9986d38
b6db79a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -868,9 +868,87 @@ SELECT * FROM cypher('issue_1630', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype | |||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('issue_1709', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -- ON CREATE SET / ON MATCH SET tests (issue #1619) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| -- | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT create_graph('merge_actions'); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Basic ON CREATE SET: first run creates the node | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Alice'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.created = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.created | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, created agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- ON MATCH SET: second run matches the existing node | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Alice'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.found = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.created, n.found | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, created agtype, found agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Both ON CREATE SET and ON MATCH SET (first run = create) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Bob'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.created = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.matched = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.created, n.matched | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, created agtype, matched agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Both ON CREATE SET and ON MATCH SET (second run = match) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Bob'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.created = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.matched = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.created, n.matched | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, created agtype, matched agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- ON MATCH SET with MERGE after MATCH (Case 1: has predecessor) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MATCH (a:Person {name: 'Alice'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (a)-[:KNOWS]->(b:Person {name: 'Charlie'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET b.source = 'merge_create' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN a.name, b.name, b.source | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (a agtype, b agtype, source agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
jrgemignani marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Multiple SET items in a single ON CREATE SET | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Dave'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.a = 1, n.b = 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.a, n.b | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, a agtype, b agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Reverse order: ON MATCH before ON CREATE should work | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Eve'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.seen = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.new = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n.name, n.new | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (name agtype, new agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Error: ON CREATE SET specified more than once | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Bad'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.a = 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON CREATE SET n.b = 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (n agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Error: ON MATCH SET specified more than once | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SELECT * FROM cypher('merge_actions', $$ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| MERGE (n:Person {name: 'Bad'}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.a = 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ON MATCH SET n.b = 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN n | ||||||||||||||||||||||||||||||||||||||||||||||||||
| $$) AS (n agtype); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| -- Chained MERGE with ON CREATE SET in non-terminal and terminal clauses | |
| SELECT * FROM cypher('merge_actions', $$ | |
| MERGE (a:Person {name: 'ChainCreateA'}) | |
| ON CREATE SET a.new = true | |
| MERGE (b:Person {name: 'ChainCreateB'}) | |
| ON CREATE SET b.new = true | |
| RETURN a.name, a.new, b.name, b.new | |
| $$) AS (a_name agtype, a_new agtype, b_name agtype, b_new agtype); | |
| -- Chained MERGE with non-terminal ON CREATE SET and terminal ON MATCH SET | |
| -- Setup an existing node to be matched by the second MERGE | |
| SELECT * FROM cypher('merge_actions', $$ | |
| CREATE (p:Person {name: 'ChainMatch', seen: false}) | |
| $$) AS (n agtype); | |
| SELECT * FROM cypher('merge_actions', $$ | |
| MERGE (a:Person {name: 'ChainCreateOnce'}) | |
| ON CREATE SET a.created = true | |
| MERGE (b:Person {name: 'ChainMatch'}) | |
| ON MATCH SET b.seen = true | |
| RETURN a.name, a.created, b.name, b.seen | |
| $$) AS (a_name agtype, a_created agtype, b_name agtype, b_seen agtype); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -321,8 +321,29 @@ static void process_simple_merge(CustomScanState *node) | |||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /* setup the scantuple that the process_path needs */ | ||||||||||||||||||||||||||||||||||
| econtext->ecxt_scantuple = sss->ss.ss_ScanTupleSlot; | ||||||||||||||||||||||||||||||||||
| mark_tts_isnull(econtext->ecxt_scantuple); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| process_path(css, NULL, true); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /* ON CREATE SET: path was just created */ | ||||||||||||||||||||||||||||||||||
| if (css->on_create_set_info) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| ExecStoreVirtualTuple(econtext->ecxt_scantuple); | ||||||||||||||||||||||||||||||||||
| apply_update_list(&css->css, css->on_create_set_info); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| /* ON MATCH SET: path already exists */ | ||||||||||||||||||||||||||||||||||
| if (css->on_match_set_info) | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
| ExprContext *econtext = node->ss.ps.ps_ExprContext; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| econtext->ecxt_scantuple = | ||||||||||||||||||||||||||||||||||
| node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| apply_update_list(&css->css, css->on_match_set_info); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -657,6 +678,11 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) | |||||||||||||||||||||||||||||||||
| free_path_entry_array(prebuilt_path_array, | ||||||||||||||||||||||||||||||||||
| path_length); | ||||||||||||||||||||||||||||||||||
| process_path(css, found_path_array, false); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /* ON MATCH SET: path was found as duplicate */ | ||||||||||||||||||||||||||||||||||
| if (css->on_match_set_info) | ||||||||||||||||||||||||||||||||||
| apply_update_list(&css->css, | ||||||||||||||||||||||||||||||||||
| css->on_match_set_info); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||
|
|
@@ -668,8 +694,19 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) | |||||||||||||||||||||||||||||||||
| css->created_paths_list = new_path; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| process_path(css, prebuilt_path_array, true); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| /* ON CREATE SET: path was just created */ | ||||||||||||||||||||||||||||||||||
| if (css->on_create_set_info) | ||||||||||||||||||||||||||||||||||
| apply_update_list(&css->css, | ||||||||||||||||||||||||||||||||||
| css->on_create_set_info); | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+729
to
+731
|
||||||||||||||||||||||||||||||||||
| if (css->on_create_set_info) | |
| apply_update_list(&css->css, | |
| css->on_create_set_info); | |
| if (css->on_create_set_info) | |
| { | |
| /* Use the scan tuple slot populated by process_path */ | |
| econtext->ecxt_scantuple = | |
| css->css.ss.ss_ScanTupleSlot; | |
| ExecStoreVirtualTuple(econtext->ecxt_scantuple); | |
| apply_update_list(&css->css, | |
| css->on_create_set_info); | |
| } |
Copilot
AI
Mar 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same issue as in the non-terminal Case 1: in the terminal Case 1 ON CREATE SET path (line 824-828), apply_update_list is called with ecxt_scantuple pointing to the lateral join's projection (set at line 789-790). The newly-created entity's values would be null in this slot. Depending on whether process_path writes entity values into this lateral join slot (which it does via merge_vertex), this may or may not work, but it is inconsistent with the Case 2 and Case 3 approaches that call ExecStoreVirtualTuple first.
| if (css->on_create_set_info) | |
| apply_update_list(&css->css, css->on_create_set_info); | |
| if (css->on_create_set_info) | |
| { | |
| /* | |
| * Materialize the current virtual tuple into the scan | |
| * slot so that apply_update_list sees the values | |
| * corresponding to the newly-created path. | |
| * | |
| * This mirrors the behavior in the other MERGE cases, | |
| * which call ExecStoreVirtualTuple before | |
| * apply_update_list. | |
| */ | |
| ExecStoreVirtualTuple(econtext->ecxt_scantuple); | |
| apply_update_list(&css->css, css->on_create_set_info); | |
| } |
Uh oh!
There was an error while loading. Please reload this page.