Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions regress/expected/cypher_merge.out
Original file line number Diff line number Diff line change
Expand Up @@ -1717,6 +1717,154 @@ SELECT * FROM cypher('issue_1907', $$ MATCH ()-[r]->() RETURN r $$) AS (r agtype
{"id": 1125899906842626, "label": "RELATED_TO", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {"property1": "something", "property2": "else"}}::edge
(1 row)

--
-- Fix issue 1446: First MERGE does not see the second MERGE's changes
--
-- When chained MERGEs appear (MATCH ... MERGE ... MERGE ...), the first
-- (non-terminal) MERGE returned rows one at a time, so the second MERGE's
-- lateral join was materialized before the first finished all iterations.
-- This caused duplicate nodes. The fix makes non-terminal MERGE eager:
-- it processes ALL input rows before returning any.
--
SELECT * FROM create_graph('issue_1446');
NOTICE: graph "issue_1446" has been created
create_graph
--------------

(1 row)

-- Reporter's exact setup: two initial nodes
SELECT * FROM cypher('issue_1446', $$ CREATE (:A), (:C) $$) AS (a agtype);
a
---
(0 rows)

-- Reporter's exact reproduction case: two chained MERGEs
-- Without fix: C is created multiple times (once per MATCH row) because the
-- second MERGE's lateral join materializes before the first MERGE finishes.
-- With fix: returns 2 rows, C is found and reused by the second MERGE.
SELECT * FROM cypher('issue_1446', $$
MATCH (x)
MERGE (x)-[:r]->(:t)
MERGE (:C)-[:r]->(:t)
RETURN x
$$) AS (a agtype);
a
------------------------------------------------------------------
{"id": 844424930131969, "label": "A", "properties": {}}::vertex
{"id": 1125899906842625, "label": "C", "properties": {}}::vertex
(2 rows)

-- Verify: A(1), C(1), t(2) = 4 nodes, 2 edges
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN labels(n) AS label, count(*) AS cnt
ORDER BY label
$$) AS (label agtype, cnt agtype);
label | cnt
-------+-----
["A"] | 1
["C"] | 1
["t"] | 2
(3 rows)

SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);
edge_count
------------
2
(1 row)

-- Test with 3 initial nodes: ensures eager buffering works for larger sets
SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
a
---
(0 rows)

SELECT * FROM cypher('issue_1446', $$ CREATE (:X), (:Y), (:Z) $$) AS (a agtype);
a
---
(0 rows)

SELECT * FROM cypher('issue_1446', $$
MATCH (n)
MERGE (n)-[:link]->(:shared)
MERGE (:hub)-[:link]->(:shared)
RETURN n
$$) AS (a agtype);
a
------------------------------------------------------------------
{"id": 1970324836974593, "label": "X", "properties": {}}::vertex
{"id": 2251799813685249, "label": "Y", "properties": {}}::vertex
{"id": 2533274790395905, "label": "Z", "properties": {}}::vertex
(3 rows)

-- Without fix: hub is created 3 times (once per MATCH row).
-- With fix: hub(1), shared(4), X(1), Y(1), Z(1) = 8 nodes, 4 edges
-- (3 n->shared edges + 1 hub->shared edge; hub reused for rows 2 & 3)
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN labels(n) AS label, count(*) AS cnt
ORDER BY label
$$) AS (label agtype, cnt agtype);
label | cnt
------------+-----
["X"] | 1
["Y"] | 1
["Z"] | 1
["hub"] | 1
["shared"] | 4
(5 rows)

SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);
edge_count
------------
4
(1 row)

-- Test chained MERGE with empty MATCH result (empty buffer scenario)
-- Without fix: non-terminal MERGE falls through to terminal logic,
-- incorrectly creating entities when MATCH returns 0 rows.
SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
a
---
(0 rows)

SELECT * FROM cypher('issue_1446', $$
MATCH (x:NonExistent)
MERGE (x)-[:r]->(:t)
MERGE (:C)-[:r]->(:t)
RETURN count(*) AS cnt
$$) AS (cnt agtype);
cnt
-----
0
(1 row)

-- Verify no nodes or edges were created
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN count(*) AS node_count
$$) AS (node_count agtype);
node_count
------------
0
(1 row)

SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);
edge_count
------------
0
(1 row)

--
-- clean up graphs
--
Expand All @@ -1735,6 +1883,11 @@ SELECT * FROM cypher('issue_1709', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype
---
(0 rows)

SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
a
---
(0 rows)

--
-- delete graphs
--
Expand Down Expand Up @@ -1812,6 +1965,26 @@ NOTICE: graph "issue_1709" has been dropped

(1 row)

SELECT drop_graph('issue_1446', true);
NOTICE: drop cascades to 12 other objects
DETAIL: drop cascades to table issue_1446._ag_label_vertex
drop cascades to table issue_1446._ag_label_edge
drop cascades to table issue_1446."A"
drop cascades to table issue_1446."C"
drop cascades to table issue_1446.r
drop cascades to table issue_1446.t
drop cascades to table issue_1446."X"
drop cascades to table issue_1446."Y"
drop cascades to table issue_1446."Z"
drop cascades to table issue_1446.link
drop cascades to table issue_1446.shared
drop cascades to table issue_1446.hub
NOTICE: graph "issue_1446" has been dropped
drop_graph
------------

(1 row)

--
-- End
--
77 changes: 77 additions & 0 deletions regress/sql/cypher_merge.sql
Original file line number Diff line number Diff line change
Expand Up @@ -785,12 +785,88 @@ SELECT * FROM cypher('issue_1907', $$ MERGE (a {name: 'Test Node A'})-[r:RELATED
-- should return properties added
SELECT * FROM cypher('issue_1907', $$ MATCH ()-[r]->() RETURN r $$) AS (r agtype);

--
-- Fix issue 1446: First MERGE does not see the second MERGE's changes
--
-- When chained MERGEs appear (MATCH ... MERGE ... MERGE ...), the first
-- (non-terminal) MERGE returned rows one at a time, so the second MERGE's
-- lateral join was materialized before the first finished all iterations.
-- This caused duplicate nodes. The fix makes non-terminal MERGE eager:
-- it processes ALL input rows before returning any.
--
SELECT * FROM create_graph('issue_1446');
-- Reporter's exact setup: two initial nodes
SELECT * FROM cypher('issue_1446', $$ CREATE (:A), (:C) $$) AS (a agtype);
-- Reporter's exact reproduction case: two chained MERGEs
-- Without fix: C is created multiple times (once per MATCH row) because the
-- second MERGE's lateral join materializes before the first MERGE finishes.
-- With fix: returns 2 rows, C is found and reused by the second MERGE.
SELECT * FROM cypher('issue_1446', $$
MATCH (x)
MERGE (x)-[:r]->(:t)
MERGE (:C)-[:r]->(:t)
RETURN x
$$) AS (a agtype);
-- Verify: A(1), C(1), t(2) = 4 nodes, 2 edges
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN labels(n) AS label, count(*) AS cnt
ORDER BY label
$$) AS (label agtype, cnt agtype);
SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);

-- Test with 3 initial nodes: ensures eager buffering works for larger sets
SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
SELECT * FROM cypher('issue_1446', $$ CREATE (:X), (:Y), (:Z) $$) AS (a agtype);
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
MERGE (n)-[:link]->(:shared)
MERGE (:hub)-[:link]->(:shared)
RETURN n
$$) AS (a agtype);
-- Without fix: hub is created 3 times (once per MATCH row).
-- With fix: hub(1), shared(4), X(1), Y(1), Z(1) = 8 nodes, 4 edges
-- (3 n->shared edges + 1 hub->shared edge; hub reused for rows 2 & 3)
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN labels(n) AS label, count(*) AS cnt
ORDER BY label
$$) AS (label agtype, cnt agtype);
SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);

-- Test chained MERGE with empty MATCH result (empty buffer scenario)
-- Without fix: non-terminal MERGE falls through to terminal logic,
-- incorrectly creating entities when MATCH returns 0 rows.
SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
SELECT * FROM cypher('issue_1446', $$
MATCH (x:NonExistent)
MERGE (x)-[:r]->(:t)
MERGE (:C)-[:r]->(:t)
RETURN count(*) AS cnt
$$) AS (cnt agtype);
-- Verify no nodes or edges were created
SELECT * FROM cypher('issue_1446', $$
MATCH (n)
RETURN count(*) AS node_count
$$) AS (node_count agtype);
SELECT * FROM cypher('issue_1446', $$
MATCH ()-[e]->()
RETURN count(*) AS edge_count
$$) AS (edge_count agtype);

--
-- clean up graphs
--
SELECT * FROM cypher('cypher_merge', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
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);

--
-- delete graphs
Expand All @@ -800,6 +876,7 @@ SELECT drop_graph('cypher_merge', true);
SELECT drop_graph('issue_1630', true);
SELECT drop_graph('issue_1691', true);
SELECT drop_graph('issue_1709', true);
SELECT drop_graph('issue_1446', true);

--
-- End
Expand Down
Loading