Skip to content
Open
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
185 changes: 185 additions & 0 deletions regress/expected/cypher_match.out
Original file line number Diff line number Diff line change
Expand Up @@ -3533,6 +3533,191 @@ SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (costs off) MATCH (x:
Filter: ((agtype_access_operator(VARIADIC ARRAY[properties, '"school"'::agtype]) = '{"name": "XYZ College", "program": {"major": "Psyc", "degree": "BSc"}}'::agtype) AND (agtype_access_operator(VARIADIC ARRAY[properties, '"phone"'::agtype]) = '[123456789, 987654321, 456987123]'::agtype))
(2 rows)

--
-- issue 2308: MATCH after CREATE returns 0 rows
--
-- When all MATCH variables are already bound from a preceding CREATE + WITH,
-- the MATCH filter quals must evaluate after CREATE, not before.
--
SELECT create_graph('issue_2308');
NOTICE: graph "issue_2308" has been created
create_graph
--------------

(1 row)

-- Reporter's exact case: CREATE + WITH + MATCH + SET + RETURN
SELECT * FROM cypher('issue_2308', $$
CREATE (a:TestB3)-[e:B3REL]->(b:TestB3)
WITH a, e, b
MATCH p = (a)-[e]->(b)
SET a.something = 'something'
RETURN a
$$) AS (a agtype);
a
----------------------------------------------------------------------------------------------
{"id": 844424930131969, "label": "TestB3", "properties": {"something": "something"}}::vertex
(1 row)

-- Bound variables, no SET
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T2)-[e:R2]->(b:T2)
WITH a, e, b
MATCH (a)-[e]->(b)
RETURN a, e, b
$$) AS (a agtype, e agtype, b agtype);
a | e | b
-------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------
{"id": 1407374883553281, "label": "T2", "properties": {}}::vertex | {"id": 1688849860263937, "label": "R2", "end_id": 1407374883553282, "start_id": 1407374883553281, "properties": {}}::edge | {"id": 1407374883553282, "label": "T2", "properties": {}}::vertex
(1 row)

-- Reversed direction: filter should reject (0 rows expected)
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T3)-[e:R3]->(b:T3)
WITH a, e, b
MATCH (b)-[e]->(a)
RETURN a
$$) AS (a agtype);
a
---
(0 rows)

-- Node-only MATCH with bound variable
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T4 {name: 'test'})
WITH a
MATCH (a)
RETURN a
$$) AS (a agtype);
a
---------------------------------------------------------------------------------
{"id": 2533274790395905, "label": "T4", "properties": {"name": "test"}}::vertex
(1 row)

-- MATCH after SET (SET is also DML, chain must be protected)
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T5 {val: 1})-[e:R5]->(b:T5 {val: 2})
$$) AS (r agtype);
r
---
(0 rows)

SELECT * FROM cypher('issue_2308', $$
MATCH (a:T5)-[e:R5]->(b:T5)
SET a.val = 10
WITH a, e, b
MATCH (a)-[e]->(b)
RETURN a.val
$$) AS (val agtype);
val
-----
10
(1 row)

SELECT drop_graph('issue_2308', true);
NOTICE: drop cascades to 11 other objects
DETAIL: drop cascades to table issue_2308._ag_label_vertex
drop cascades to table issue_2308._ag_label_edge
drop cascades to table issue_2308."TestB3"
drop cascades to table issue_2308."B3REL"
drop cascades to table issue_2308."T2"
drop cascades to table issue_2308."R2"
drop cascades to table issue_2308."T3"
drop cascades to table issue_2308."R3"
drop cascades to table issue_2308."T4"
drop cascades to table issue_2308."T5"
drop cascades to table issue_2308."R5"
NOTICE: graph "issue_2308" has been dropped
drop_graph
------------

(1 row)

--
-- Issue 2193: CREATE ... WITH ... MATCH on brand-new label returns 0 rows
-- on first execution because match_check_valid_label() runs before
-- transform_prev_cypher_clause() creates the label table.
--
SELECT create_graph('issue_2193');
NOTICE: graph "issue_2193" has been created
create_graph
--------------

(1 row)

-- Reporter's exact case: CREATE two Person nodes, then MATCH on Person
-- Should return 2 rows on the very first execution
SELECT * FROM cypher('issue_2193', $$
CREATE (a:Person {name: 'Jane', livesIn: 'London'}),
(b:Person {name: 'Tom', livesIn: 'Copenhagen'})
WITH a, b
MATCH (p:Person)
RETURN p.name ORDER BY p.name
$$) AS (result agtype);
result
--------
"Jane"
"Tom"
(2 rows)

-- Single CREATE + MATCH on brand-new label
SELECT * FROM cypher('issue_2193', $$
CREATE (a:City {name: 'Berlin'})
WITH a
MATCH (c:City)
RETURN c.name
$$) AS (result agtype);
result
----------
"Berlin"
(1 row)

-- MATCH on a label that now exists (second execution) still works
SELECT * FROM cypher('issue_2193', $$
CREATE (a:City {name: 'Paris'})
WITH a
MATCH (c:City)
RETURN c.name ORDER BY c.name
$$) AS (result agtype);
result
----------
"Berlin"
"Paris"
(2 rows)

-- MATCH on non-existent label without DML predecessor still returns 0 rows
SELECT * FROM cypher('issue_2193', $$
MATCH (x:NonExistentLabel)
RETURN x
$$) AS (result agtype);
result
--------
(0 rows)

-- MATCH on non-existent label after DML predecessor still returns 0 rows
-- and MATCH-introduced variable (p) is properly registered
SELECT * FROM cypher('issue_2193', $$
CREATE (a:Person {name: 'Alice'})
WITH a
MATCH (p:NonExistentLabel)
RETURN p
$$) AS (result agtype);
result
--------
(0 rows)

SELECT drop_graph('issue_2193', true);
NOTICE: drop cascades to 4 other objects
DETAIL: drop cascades to table issue_2193._ag_label_vertex
drop cascades to table issue_2193._ag_label_edge
drop cascades to table issue_2193."Person"
drop cascades to table issue_2193."City"
NOTICE: graph "issue_2193" has been dropped
drop_graph
------------

(1 row)

--
-- Clean up
--
Expand Down
105 changes: 105 additions & 0 deletions regress/sql/cypher_match.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1437,6 +1437,111 @@ SELECT count(*) FROM cypher('test_enable_containment', $$ MATCH p=(x:Customer)-[
SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (costs off) MATCH (x:Customer)-[:bought ={store: 'Amazon', addr:{city: 'Vancouver', street: 30}}]->(y:Product) RETURN 0 $$) as (a agtype);
SELECT * FROM cypher('test_enable_containment', $$ EXPLAIN (costs off) MATCH (x:Customer ={school: { name: 'XYZ College',program: { major: 'Psyc', degree: 'BSc'} },phone: [ 123456789, 987654321, 456987123 ]}) RETURN 0 $$) as (a agtype);

--
-- issue 2308: MATCH after CREATE returns 0 rows
--
-- When all MATCH variables are already bound from a preceding CREATE + WITH,
-- the MATCH filter quals must evaluate after CREATE, not before.
--
SELECT create_graph('issue_2308');

-- Reporter's exact case: CREATE + WITH + MATCH + SET + RETURN
SELECT * FROM cypher('issue_2308', $$
CREATE (a:TestB3)-[e:B3REL]->(b:TestB3)
WITH a, e, b
MATCH p = (a)-[e]->(b)
SET a.something = 'something'
RETURN a
$$) AS (a agtype);

-- Bound variables, no SET
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T2)-[e:R2]->(b:T2)
WITH a, e, b
MATCH (a)-[e]->(b)
RETURN a, e, b
$$) AS (a agtype, e agtype, b agtype);

-- Reversed direction: filter should reject (0 rows expected)
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T3)-[e:R3]->(b:T3)
WITH a, e, b
MATCH (b)-[e]->(a)
RETURN a
$$) AS (a agtype);

-- Node-only MATCH with bound variable
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T4 {name: 'test'})
WITH a
MATCH (a)
RETURN a
$$) AS (a agtype);

-- MATCH after SET (SET is also DML, chain must be protected)
SELECT * FROM cypher('issue_2308', $$
CREATE (a:T5 {val: 1})-[e:R5]->(b:T5 {val: 2})
$$) AS (r agtype);
SELECT * FROM cypher('issue_2308', $$
MATCH (a:T5)-[e:R5]->(b:T5)
SET a.val = 10
WITH a, e, b
MATCH (a)-[e]->(b)
RETURN a.val
$$) AS (val agtype);

SELECT drop_graph('issue_2308', true);

--
-- Issue 2193: CREATE ... WITH ... MATCH on brand-new label returns 0 rows
-- on first execution because match_check_valid_label() runs before
-- transform_prev_cypher_clause() creates the label table.
--
SELECT create_graph('issue_2193');

-- Reporter's exact case: CREATE two Person nodes, then MATCH on Person
-- Should return 2 rows on the very first execution
SELECT * FROM cypher('issue_2193', $$
CREATE (a:Person {name: 'Jane', livesIn: 'London'}),
(b:Person {name: 'Tom', livesIn: 'Copenhagen'})
WITH a, b
MATCH (p:Person)
RETURN p.name ORDER BY p.name
$$) AS (result agtype);

-- Single CREATE + MATCH on brand-new label
SELECT * FROM cypher('issue_2193', $$
CREATE (a:City {name: 'Berlin'})
WITH a
MATCH (c:City)
RETURN c.name
$$) AS (result agtype);

-- MATCH on a label that now exists (second execution) still works
SELECT * FROM cypher('issue_2193', $$
CREATE (a:City {name: 'Paris'})
WITH a
MATCH (c:City)
RETURN c.name ORDER BY c.name
$$) AS (result agtype);
Comment on lines 1504 to 1526
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regression tests added for issue_2193 assert a specific row order (e.g., Jane then Tom; Berlin then Paris) but the Cypher queries don't include an ORDER BY. Without ordering, result order can vary with plan changes (seq scan order, optimizer choices), making the test output potentially flaky. Consider adding an ORDER BY on the returned expression (or changing assertions to use counts/sets) to make the expected output deterministic.

Copilot uses AI. Check for mistakes.

-- MATCH on non-existent label without DML predecessor still returns 0 rows
SELECT * FROM cypher('issue_2193', $$
MATCH (x:NonExistentLabel)
RETURN x
$$) AS (result agtype);

Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new label-deferral logic introduces a new branch for “DML predecessor + invalid label”. There’s no regression test covering a query that (1) has a DML predecessor and (2) references a non-existent label in MATCH while still returning a MATCH-introduced variable (e.g., CREATE ... WITH a MATCH (p:NoSuchLabel) RETURN p). Adding a test for this case would help catch planner/namespace regressions in the deferred-label path.

Suggested change
-- MATCH on non-existent label after DML predecessor still returns 0 rows
SELECT * FROM cypher('issue_2193', $$
CREATE (a:Person {name: 'Alice'})
WITH a
MATCH (p:NonExistentLabel)
RETURN p
$$) AS (result agtype);

Copilot uses AI. Check for mistakes.
-- MATCH on non-existent label after DML predecessor still returns 0 rows
-- and MATCH-introduced variable (p) is properly registered
SELECT * FROM cypher('issue_2193', $$
CREATE (a:Person {name: 'Alice'})
WITH a
MATCH (p:NonExistentLabel)
RETURN p
$$) AS (result agtype);

SELECT drop_graph('issue_2193', true);

--
-- Clean up
--
Expand Down
Loading