Skip to content

Commit 7e07f32

Browse files
authored
Merge branch 'master' into add_index_scan
2 parents 0ffedcd + d0741d8 commit 7e07f32

6 files changed

Lines changed: 288 additions & 0 deletions

File tree

regress/expected/cypher_merge.out

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,114 @@ $$) AS (edge_count agtype);
18651865
0
18661866
(1 row)
18671867

1868+
-- Issue 1954: CREATE + WITH + MERGE causes "vertex was deleted" error
1869+
-- when the number of input rows exceeds the snapshot's command ID window.
1870+
-- entity_exists() used a stale curcid, making recently-created vertices
1871+
-- invisible on later iterations.
1872+
--
1873+
SELECT * FROM create_graph('issue_1954');
1874+
NOTICE: graph "issue_1954" has been created
1875+
create_graph
1876+
--------------
1877+
1878+
(1 row)
1879+
1880+
-- Setup: create source nodes and relationships (3 rows to trigger the bug)
1881+
SELECT * FROM cypher('issue_1954', $$
1882+
CREATE (:A {name: 'a1'})-[:R]->(:B {name: 'b1'}),
1883+
(:A {name: 'a2'})-[:R]->(:B {name: 'b2'}),
1884+
(:A {name: 'a3'})-[:R]->(:B {name: 'b3'})
1885+
$$) AS (result agtype);
1886+
result
1887+
--------
1888+
(0 rows)
1889+
1890+
-- This query would fail with "vertex assigned to variable c was deleted"
1891+
-- on the 3rd row before the fix.
1892+
SELECT * FROM cypher('issue_1954', $$
1893+
MATCH (a:A)-[:R]->(b:B)
1894+
CREATE (c:C {name: a.name + '|' + b.name})
1895+
WITH a, b, c
1896+
MERGE (a)-[:LINK]->(c)
1897+
RETURN a.name, b.name, c.name
1898+
ORDER BY a.name
1899+
$$) AS (a agtype, b agtype, c agtype);
1900+
a | b | c
1901+
------+------+---------
1902+
"a1" | "b1" | "a1|b1"
1903+
"a2" | "b2" | "a2|b2"
1904+
"a3" | "b3" | "a3|b3"
1905+
(3 rows)
1906+
1907+
-- Verify edges were created
1908+
SELECT * FROM cypher('issue_1954', $$
1909+
MATCH (a:A)-[:LINK]->(c:C)
1910+
RETURN a.name, c.name
1911+
ORDER BY a.name
1912+
$$) AS (a agtype, c agtype);
1913+
a | c
1914+
------+---------
1915+
"a1" | "a1|b1"
1916+
"a2" | "a2|b2"
1917+
"a3" | "a3|b3"
1918+
(3 rows)
1919+
1920+
-- Test with two MERGEs (more complex case from the original report)
1921+
SELECT * FROM cypher('issue_1954', $$
1922+
MATCH ()-[e:LINK]->() DELETE e
1923+
$$) AS (result agtype);
1924+
result
1925+
--------
1926+
(0 rows)
1927+
1928+
SELECT * FROM cypher('issue_1954', $$
1929+
MATCH (c:C) DELETE c
1930+
$$) AS (result agtype);
1931+
result
1932+
--------
1933+
(0 rows)
1934+
1935+
SELECT * FROM cypher('issue_1954', $$
1936+
MATCH (a:A)-[:R]->(b:B)
1937+
CREATE (c:C {name: a.name + '|' + b.name})
1938+
WITH a, b, c
1939+
MERGE (a)-[:LINK1]->(c)
1940+
MERGE (b)-[:LINK2]->(c)
1941+
RETURN a.name, b.name, c.name
1942+
ORDER BY a.name
1943+
$$) AS (a agtype, b agtype, c agtype);
1944+
a | b | c
1945+
------+------+---------
1946+
"a1" | "b1" | "a1|b1"
1947+
"a2" | "b2" | "a2|b2"
1948+
"a3" | "b3" | "a3|b3"
1949+
(3 rows)
1950+
1951+
-- Verify both sets of edges
1952+
SELECT * FROM cypher('issue_1954', $$
1953+
MATCH (a:A)-[:LINK1]->(c:C)
1954+
RETURN a.name, c.name
1955+
ORDER BY a.name
1956+
$$) AS (a agtype, c agtype);
1957+
a | c
1958+
------+---------
1959+
"a1" | "a1|b1"
1960+
"a2" | "a2|b2"
1961+
"a3" | "a3|b3"
1962+
(3 rows)
1963+
1964+
SELECT * FROM cypher('issue_1954', $$
1965+
MATCH (b:B)-[:LINK2]->(c:C)
1966+
RETURN b.name, c.name
1967+
ORDER BY b.name
1968+
$$) AS (b agtype, c agtype);
1969+
b | c
1970+
------+---------
1971+
"b1" | "a1|b1"
1972+
"b2" | "a2|b2"
1973+
"b3" | "a3|b3"
1974+
(3 rows)
1975+
18681976
--
18691977
-- clean up graphs
18701978
--
@@ -1888,6 +1996,11 @@ SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype
18881996
---
18891997
(0 rows)
18901998

1999+
SELECT * FROM cypher('issue_1954', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
2000+
a
2001+
---
2002+
(0 rows)
2003+
18912004
--
18922005
-- delete graphs
18932006
--
@@ -1985,6 +2098,23 @@ NOTICE: graph "issue_1446" has been dropped
19852098

19862099
(1 row)
19872100

2101+
SELECT drop_graph('issue_1954', true);
2102+
NOTICE: drop cascades to 9 other objects
2103+
DETAIL: drop cascades to table issue_1954._ag_label_vertex
2104+
drop cascades to table issue_1954._ag_label_edge
2105+
drop cascades to table issue_1954."A"
2106+
drop cascades to table issue_1954."R"
2107+
drop cascades to table issue_1954."B"
2108+
drop cascades to table issue_1954."C"
2109+
drop cascades to table issue_1954."LINK"
2110+
drop cascades to table issue_1954."LINK1"
2111+
drop cascades to table issue_1954."LINK2"
2112+
NOTICE: graph "issue_1954" has been dropped
2113+
drop_graph
2114+
------------
2115+
2116+
(1 row)
2117+
19882118
--
19892119
-- End
19902120
--

regress/expected/scan.out

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,47 @@ $$) AS t(a agtype, b agtype, c agtype);
294294
" \" \" ' ' " | " ' ' \" \" " | " / / \\ \b \f \n \r \t "
295295
(1 row)
296296

297+
-- doubled-quote escape sequences for SQL driver compatibility (issue #2222)
298+
SELECT * FROM cypher('scan', $$
299+
RETURN 'isn''t'
300+
$$) AS t(a agtype);
301+
a
302+
---------
303+
"isn't"
304+
(1 row)
305+
306+
SELECT * FROM cypher('scan', $$
307+
RETURN '''hello'
308+
$$) AS t(a agtype);
309+
a
310+
----------
311+
"'hello"
312+
(1 row)
313+
314+
SELECT * FROM cypher('scan', $$
315+
RETURN 'hello'''
316+
$$) AS t(a agtype);
317+
a
318+
----------
319+
"hello'"
320+
(1 row)
321+
322+
SELECT * FROM cypher('scan', $$
323+
RETURN 'it''s a ''test'''
324+
$$) AS t(a agtype);
325+
a
326+
-----------------
327+
"it's a 'test'"
328+
(1 row)
329+
330+
SELECT * FROM cypher('scan', $$
331+
RETURN "she said ""hello"""
332+
$$) AS t(a agtype);
333+
a
334+
----------------------
335+
"she said \"hello\""
336+
(1 row)
337+
297338
-- invalid escape sequence
298339
SELECT * FROM cypher('scan', $$
299340
RETURN "\a"

regress/sql/cypher_merge.sql

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,13 +860,77 @@ SELECT * FROM cypher('issue_1446', $$
860860
RETURN count(*) AS edge_count
861861
$$) AS (edge_count agtype);
862862

863+
-- Issue 1954: CREATE + WITH + MERGE causes "vertex was deleted" error
864+
-- when the number of input rows exceeds the snapshot's command ID window.
865+
-- entity_exists() used a stale curcid, making recently-created vertices
866+
-- invisible on later iterations.
867+
--
868+
SELECT * FROM create_graph('issue_1954');
869+
870+
-- Setup: create source nodes and relationships (3 rows to trigger the bug)
871+
SELECT * FROM cypher('issue_1954', $$
872+
CREATE (:A {name: 'a1'})-[:R]->(:B {name: 'b1'}),
873+
(:A {name: 'a2'})-[:R]->(:B {name: 'b2'}),
874+
(:A {name: 'a3'})-[:R]->(:B {name: 'b3'})
875+
$$) AS (result agtype);
876+
877+
-- This query would fail with "vertex assigned to variable c was deleted"
878+
-- on the 3rd row before the fix.
879+
SELECT * FROM cypher('issue_1954', $$
880+
MATCH (a:A)-[:R]->(b:B)
881+
CREATE (c:C {name: a.name + '|' + b.name})
882+
WITH a, b, c
883+
MERGE (a)-[:LINK]->(c)
884+
RETURN a.name, b.name, c.name
885+
ORDER BY a.name
886+
$$) AS (a agtype, b agtype, c agtype);
887+
888+
-- Verify edges were created
889+
SELECT * FROM cypher('issue_1954', $$
890+
MATCH (a:A)-[:LINK]->(c:C)
891+
RETURN a.name, c.name
892+
ORDER BY a.name
893+
$$) AS (a agtype, c agtype);
894+
895+
-- Test with two MERGEs (more complex case from the original report)
896+
SELECT * FROM cypher('issue_1954', $$
897+
MATCH ()-[e:LINK]->() DELETE e
898+
$$) AS (result agtype);
899+
SELECT * FROM cypher('issue_1954', $$
900+
MATCH (c:C) DELETE c
901+
$$) AS (result agtype);
902+
903+
SELECT * FROM cypher('issue_1954', $$
904+
MATCH (a:A)-[:R]->(b:B)
905+
CREATE (c:C {name: a.name + '|' + b.name})
906+
WITH a, b, c
907+
MERGE (a)-[:LINK1]->(c)
908+
MERGE (b)-[:LINK2]->(c)
909+
RETURN a.name, b.name, c.name
910+
ORDER BY a.name
911+
$$) AS (a agtype, b agtype, c agtype);
912+
913+
-- Verify both sets of edges
914+
SELECT * FROM cypher('issue_1954', $$
915+
MATCH (a:A)-[:LINK1]->(c:C)
916+
RETURN a.name, c.name
917+
ORDER BY a.name
918+
$$) AS (a agtype, c agtype);
919+
920+
SELECT * FROM cypher('issue_1954', $$
921+
MATCH (b:B)-[:LINK2]->(c:C)
922+
RETURN b.name, c.name
923+
ORDER BY b.name
924+
$$) AS (b agtype, c agtype);
925+
863926
--
864927
-- clean up graphs
865928
--
866929
SELECT * FROM cypher('cypher_merge', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
867930
SELECT * FROM cypher('issue_1630', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
868931
SELECT * FROM cypher('issue_1709', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
869932
SELECT * FROM cypher('issue_1446', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
933+
SELECT * FROM cypher('issue_1954', $$ MATCH (n) DETACH DELETE n $$) AS (a agtype);
870934

871935
--
872936
-- delete graphs
@@ -877,6 +941,7 @@ SELECT drop_graph('issue_1630', true);
877941
SELECT drop_graph('issue_1691', true);
878942
SELECT drop_graph('issue_1709', true);
879943
SELECT drop_graph('issue_1446', true);
944+
SELECT drop_graph('issue_1954', true);
880945

881946
--
882947
-- End

regress/sql/scan.sql

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,27 @@ SELECT * FROM cypher('scan', $$
202202
RETURN " \" \" ' \' ", ' \' \' " \" ', " / \/ \\ \b \f \n \r \t "
203203
$$) AS t(a agtype, b agtype, c agtype);
204204

205+
-- doubled-quote escape sequences for SQL driver compatibility (issue #2222)
206+
SELECT * FROM cypher('scan', $$
207+
RETURN 'isn''t'
208+
$$) AS t(a agtype);
209+
210+
SELECT * FROM cypher('scan', $$
211+
RETURN '''hello'
212+
$$) AS t(a agtype);
213+
214+
SELECT * FROM cypher('scan', $$
215+
RETURN 'hello'''
216+
$$) AS t(a agtype);
217+
218+
SELECT * FROM cypher('scan', $$
219+
RETURN 'it''s a ''test'''
220+
$$) AS t(a agtype);
221+
222+
SELECT * FROM cypher('scan', $$
223+
RETURN "she said ""hello"""
224+
$$) AS t(a agtype);
225+
205226
-- invalid escape sequence
206227
SELECT * FROM cypher('scan', $$
207228
RETURN "\a"

src/backend/executor/cypher_utils.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id)
210210
bool result = true;
211211
TupleTableSlot *slot;
212212
Oid index_oid = InvalidOid;
213+
CommandId saved_curcid;
213214

214215
/*
215216
* Extract the label id from the graph id and get the table name
@@ -221,6 +222,23 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id)
221222
ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber,
222223
F_GRAPHIDEQ, GRAPHID_GET_DATUM(id));
223224

225+
/*
226+
* Temporarily advance the snapshot's curcid so that entities inserted
227+
* by preceding clauses (e.g., CREATE) in the same query are visible.
228+
* CREATE calls CommandCounterIncrement() which advances the global
229+
* CID, but does not update es_snapshot->curcid. The Decrement/Increment
230+
* CID macros used by the executors can leave curcid behind the global
231+
* CID, making recently created entities invisible to this scan.
232+
*
233+
* Use Max to ensure we never decrease curcid. The executor macros
234+
* (Increment_Estate_CommandId) can push curcid above the global CID,
235+
* and blindly assigning GetCurrentCommandId could make tuples that
236+
* are visible at the current curcid become invisible.
237+
*/
238+
saved_curcid = estate->es_snapshot->curcid;
239+
estate->es_snapshot->curcid = Max(saved_curcid,
240+
GetCurrentCommandId(false));
241+
224242
rel = table_open(label->relation, RowExclusiveLock);
225243

226244
index_oid = RelationGetPrimaryKeyIndex(rel, false);
@@ -286,6 +304,9 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id)
286304

287305
table_close(rel, RowExclusiveLock);
288306

307+
/* Restore the original curcid */
308+
estate->es_snapshot->curcid = saved_curcid;
309+
289310
return result;
290311
}
291312

src/backend/parser/ag_scanner.l

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,8 @@ dquote \"
195195
dqchars [^"\\]+
196196
squote '
197197
sqchars [^'\\]+
198+
esdquote {dquote}{dquote}
199+
essquote {squote}{squote}
198200
esascii \\["'/\\bfnrt]
199201
esasciifail \\[^Uu]?
200202
esunicode \\(U{hexdigit}{8}|u{hexdigit}{4})
@@ -420,6 +422,14 @@ ag_token token;
420422
strbuf_append_buf(&yyextra.literal_buf, yytext, yyleng);
421423
}
422424

425+
<dqstr>{esdquote} {
426+
strbuf_append_char(&yyextra.literal_buf, '"');
427+
}
428+
429+
<sqstr>{essquote} {
430+
strbuf_append_char(&yyextra.literal_buf, '\'');
431+
}
432+
423433
<dqstr,sqstr>{esascii} {
424434
char c;
425435

0 commit comments

Comments
 (0)