Skip to content

Commit 3aeabdc

Browse files
gregfeliceclaude
andcommitted
Fix @> vs @>> for =properties form with PREPARE and add tests
When MATCH uses the =properties form (e.g., MATCH (n = $props)), the enable_containment=on path correctly uses @>> (top-level containment). The parameter fallback path unconditionally used @> (deep containment), ignoring the use_equals flag. Fix the fallback to mirror the enable_containment path by selecting @>> when use_equals is set. Add regression tests for =properties form with PREPARE for both vertices and edges, with enable_containment on and off. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e4fba70 commit 3aeabdc

3 files changed

Lines changed: 83 additions & 6 deletions

File tree

regress/expected/cypher_match.out

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,9 +3628,9 @@ drop cascades to table issue_2308."T4"
36283628
drop cascades to table issue_2308."T5"
36293629
drop cascades to table issue_2308."R5"
36303630
NOTICE: graph "issue_2308" has been dropped
3631-
drop_graph
3631+
drop_graph
36323632
------------
3633-
3633+
36343634
(1 row)
36353635

36363636
-- Issue 1964
@@ -3642,9 +3642,9 @@ NOTICE: graph "issue_2308" has been dropped
36423642
--
36433643
SELECT create_graph('issue_1964');
36443644
NOTICE: graph "issue_1964" has been created
3645-
create_graph
3645+
create_graph
36463646
--------------
3647-
3647+
36483648
(1 row)
36493649

36503650
SELECT * FROM cypher('issue_1964', $$
@@ -3705,6 +3705,38 @@ EXECUTE issue_1964_vertex_on('{"props": {"name": "Alice"}}');
37053705
(2 rows)
37063706

37073707
DEALLOCATE issue_1964_vertex_on;
3708+
-- Test =properties form with PREPARE (uses @>> top-level containment)
3709+
SET age.enable_containment = off;
3710+
PREPARE issue_1964_vertex_eq(agtype) AS
3711+
SELECT * FROM cypher('issue_1964',
3712+
$$MATCH (n = $props) RETURN n $$, $1) AS (p agtype);
3713+
EXECUTE issue_1964_vertex_eq('{"props": {"name": "Alice", "age": 25}}');
3714+
p
3715+
---
3716+
(0 rows)
3717+
3718+
DEALLOCATE issue_1964_vertex_eq;
3719+
PREPARE issue_1964_edge_eq(agtype) AS
3720+
SELECT * FROM cypher('issue_1964',
3721+
$$MATCH ()-[r = $props]->() RETURN r $$, $1) AS (p agtype);
3722+
EXECUTE issue_1964_edge_eq('{"props": {"since": 2020}}');
3723+
p
3724+
-----------------------------------------------------------------------------------------------------------------------------------------
3725+
{"id": 1125899906842625, "label": "KNOWS", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {"since": 2020}}::edge
3726+
(1 row)
3727+
3728+
DEALLOCATE issue_1964_edge_eq;
3729+
-- Same with enable_containment on
3730+
SET age.enable_containment = on;
3731+
PREPARE issue_1964_vertex_eq_on(agtype) AS
3732+
SELECT * FROM cypher('issue_1964',
3733+
$$MATCH (n = $props) RETURN n $$, $1) AS (p agtype);
3734+
EXECUTE issue_1964_vertex_eq_on('{"props": {"name": "Alice", "age": 25}}');
3735+
p
3736+
---
3737+
(0 rows)
3738+
3739+
DEALLOCATE issue_1964_vertex_eq_on;
37083740
--
37093741
-- Clean up
37103742
--

regress/sql/cypher_match.sql

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,30 @@ PREPARE issue_1964_vertex_on(agtype) AS
15341534
EXECUTE issue_1964_vertex_on('{"props": {"name": "Alice"}}');
15351535
DEALLOCATE issue_1964_vertex_on;
15361536

1537+
-- Test =properties form with PREPARE (uses @>> top-level containment)
1538+
SET age.enable_containment = off;
1539+
1540+
PREPARE issue_1964_vertex_eq(agtype) AS
1541+
SELECT * FROM cypher('issue_1964',
1542+
$$MATCH (n = $props) RETURN n $$, $1) AS (p agtype);
1543+
EXECUTE issue_1964_vertex_eq('{"props": {"name": "Alice", "age": 25}}');
1544+
DEALLOCATE issue_1964_vertex_eq;
1545+
1546+
PREPARE issue_1964_edge_eq(agtype) AS
1547+
SELECT * FROM cypher('issue_1964',
1548+
$$MATCH ()-[r = $props]->() RETURN r $$, $1) AS (p agtype);
1549+
EXECUTE issue_1964_edge_eq('{"props": {"since": 2020}}');
1550+
DEALLOCATE issue_1964_edge_eq;
1551+
1552+
-- Same with enable_containment on
1553+
SET age.enable_containment = on;
1554+
1555+
PREPARE issue_1964_vertex_eq_on(agtype) AS
1556+
SELECT * FROM cypher('issue_1964',
1557+
$$MATCH (n = $props) RETURN n $$, $1) AS (p agtype);
1558+
EXECUTE issue_1964_vertex_eq_on('{"props": {"name": "Alice", "age": 25}}');
1559+
DEALLOCATE issue_1964_vertex_eq_on;
1560+
15371561
--
15381562
-- Clean up
15391563
--

src/backend/parser/cypher_clause.c

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4269,8 +4269,29 @@ static Node *create_property_constraints(cypher_parsestate *cpstate,
42694269
*/
42704270
if (is_ag_node(property_constraints, cypher_param))
42714271
{
4272-
return (Node *)make_op(pstate, list_make1(makeString("@>")),
4273-
prop_expr, const_expr, last_srf, -1);
4272+
/*
4273+
* Use @>> (top-level containment) for =properties form,
4274+
* @> (deep containment) otherwise — matching the
4275+
* enable_containment=on path above.
4276+
*/
4277+
if ((entity->type == ENT_VERTEX &&
4278+
entity->entity.node->use_equals) ||
4279+
((entity->type == ENT_EDGE ||
4280+
entity->type == ENT_VLE_EDGE) &&
4281+
entity->entity.rel->use_equals))
4282+
{
4283+
return (Node *)make_op(pstate,
4284+
list_make1(makeString("@>>")),
4285+
prop_expr, const_expr,
4286+
last_srf, -1);
4287+
}
4288+
else
4289+
{
4290+
return (Node *)make_op(pstate,
4291+
list_make1(makeString("@>")),
4292+
prop_expr, const_expr,
4293+
last_srf, -1);
4294+
}
42744295
}
42754296
return (Node *)transform_map_to_ind(
42764297
cpstate, entity, (cypher_map *)property_constraints);

0 commit comments

Comments
 (0)