Skip to content

Commit e4fba70

Browse files
gregfeliceclaude
andcommitted
Fix crash in PREPARE with property parameter when enable_containment is off
When age.enable_containment is set to off, executing a PREPARE statement with a property parameter (e.g., MATCH (n $props) RETURN n) causes a segfault. The crash occurs in transform_map_to_ind_recursive because the property_constraints node is a cypher_param, not a cypher_map, but is blindly cast to cypher_map and its keyvals field is dereferenced. Three fixes: - In create_property_constraints, when enable_containment is off and the constraint is a cypher_param, fall back to the containment operator (@>) since map decomposition requires known keys at parse time. - In transform_match_entities, guard the keep_null assignment for both vertex and edge property constraints with is_ag_node checks to avoid writing to the wrong struct layout. Fixes #1964 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 217467a commit e4fba70

3 files changed

Lines changed: 148 additions & 4 deletions

File tree

regress/expected/cypher_match.out

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,11 +3628,83 @@ 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+
3634+
(1 row)
3635+
3636+
-- Issue 1964
3637+
--
3638+
-- PREPARE with property parameter ($props) crashed the server when
3639+
-- age.enable_containment was set to off. The crash was in
3640+
-- transform_map_to_ind_recursive which blindly cast cypher_param
3641+
-- nodes to cypher_map, accessing invalid memory.
3642+
--
3643+
SELECT create_graph('issue_1964');
3644+
NOTICE: graph "issue_1964" has been created
3645+
create_graph
3646+
--------------
3647+
3648+
(1 row)
3649+
3650+
SELECT * FROM cypher('issue_1964', $$
3651+
CREATE (:Person {name: 'Alice', age: 30}),
3652+
(:Person {name: 'Bob', age: 25})
3653+
$$) AS (result agtype);
3654+
result
3655+
--------
3656+
(0 rows)
3657+
3658+
SELECT * FROM cypher('issue_1964', $$
3659+
CREATE (:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(:Person {name: 'Bob'})
3660+
$$) AS (result agtype);
3661+
result
3662+
--------
3663+
(0 rows)
3664+
3665+
-- Test PREPARE with enable_containment off (was crashing)
3666+
SET age.enable_containment = off;
3667+
PREPARE issue_1964_vertex(agtype) AS
3668+
SELECT * FROM cypher('issue_1964',
3669+
$$MATCH (n $props) RETURN n $$, $1) AS (p agtype);
3670+
EXECUTE issue_1964_vertex('{"props": {"name": "Alice"}}');
3671+
p
3672+
------------------------------------------------------------------------------------------------
3673+
{"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": "Alice"}}::vertex
3674+
{"id": 844424930131971, "label": "Person", "properties": {"name": "Alice"}}::vertex
3675+
(2 rows)
3676+
3677+
EXECUTE issue_1964_vertex('{"props": {"age": 25}}');
3678+
p
3679+
----------------------------------------------------------------------------------------------
3680+
{"id": 844424930131970, "label": "Person", "properties": {"age": 25, "name": "Bob"}}::vertex
3681+
(1 row)
3682+
3683+
DEALLOCATE issue_1964_vertex;
3684+
-- Test edge property parameter with enable_containment off
3685+
PREPARE issue_1964_edge(agtype) AS
3686+
SELECT * FROM cypher('issue_1964',
3687+
$$MATCH ()-[r $props]->() RETURN r $$, $1) AS (p agtype);
3688+
EXECUTE issue_1964_edge('{"props": {"since": 2020}}');
3689+
p
3690+
-----------------------------------------------------------------------------------------------------------------------------------------
3691+
{"id": 1125899906842625, "label": "KNOWS", "end_id": 844424930131972, "start_id": 844424930131971, "properties": {"since": 2020}}::edge
36343692
(1 row)
36353693

3694+
DEALLOCATE issue_1964_edge;
3695+
-- Verify enable_containment on still works with PREPARE
3696+
SET age.enable_containment = on;
3697+
PREPARE issue_1964_vertex_on(agtype) AS
3698+
SELECT * FROM cypher('issue_1964',
3699+
$$MATCH (n $props) RETURN n $$, $1) AS (p agtype);
3700+
EXECUTE issue_1964_vertex_on('{"props": {"name": "Alice"}}');
3701+
p
3702+
------------------------------------------------------------------------------------------------
3703+
{"id": 844424930131969, "label": "Person", "properties": {"age": 30, "name": "Alice"}}::vertex
3704+
{"id": 844424930131971, "label": "Person", "properties": {"name": "Alice"}}::vertex
3705+
(2 rows)
3706+
3707+
DEALLOCATE issue_1964_vertex_on;
36363708
--
36373709
-- Clean up
36383710
--
@@ -3721,6 +3793,18 @@ NOTICE: graph "issue_1393" has been dropped
37213793

37223794
(1 row)
37233795

3796+
SELECT drop_graph('issue_1964', true);
3797+
NOTICE: drop cascades to 4 other objects
3798+
DETAIL: drop cascades to table issue_1964._ag_label_vertex
3799+
drop cascades to table issue_1964._ag_label_edge
3800+
drop cascades to table issue_1964."Person"
3801+
drop cascades to table issue_1964."KNOWS"
3802+
NOTICE: graph "issue_1964" has been dropped
3803+
drop_graph
3804+
------------
3805+
3806+
(1 row)
3807+
37243808
--
37253809
-- End
37263810
--

regress/sql/cypher_match.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1491,6 +1491,48 @@ SELECT * FROM cypher('issue_2308', $$
14911491
$$) AS (val agtype);
14921492

14931493
SELECT drop_graph('issue_2308', true);
1494+
-- Issue 1964
1495+
--
1496+
-- PREPARE with property parameter ($props) crashed the server when
1497+
-- age.enable_containment was set to off. The crash was in
1498+
-- transform_map_to_ind_recursive which blindly cast cypher_param
1499+
-- nodes to cypher_map, accessing invalid memory.
1500+
--
1501+
1502+
SELECT create_graph('issue_1964');
1503+
SELECT * FROM cypher('issue_1964', $$
1504+
CREATE (:Person {name: 'Alice', age: 30}),
1505+
(:Person {name: 'Bob', age: 25})
1506+
$$) AS (result agtype);
1507+
SELECT * FROM cypher('issue_1964', $$
1508+
CREATE (:Person {name: 'Alice'})-[:KNOWS {since: 2020}]->(:Person {name: 'Bob'})
1509+
$$) AS (result agtype);
1510+
1511+
-- Test PREPARE with enable_containment off (was crashing)
1512+
SET age.enable_containment = off;
1513+
1514+
PREPARE issue_1964_vertex(agtype) AS
1515+
SELECT * FROM cypher('issue_1964',
1516+
$$MATCH (n $props) RETURN n $$, $1) AS (p agtype);
1517+
EXECUTE issue_1964_vertex('{"props": {"name": "Alice"}}');
1518+
EXECUTE issue_1964_vertex('{"props": {"age": 25}}');
1519+
DEALLOCATE issue_1964_vertex;
1520+
1521+
-- Test edge property parameter with enable_containment off
1522+
PREPARE issue_1964_edge(agtype) AS
1523+
SELECT * FROM cypher('issue_1964',
1524+
$$MATCH ()-[r $props]->() RETURN r $$, $1) AS (p agtype);
1525+
EXECUTE issue_1964_edge('{"props": {"since": 2020}}');
1526+
DEALLOCATE issue_1964_edge;
1527+
1528+
-- Verify enable_containment on still works with PREPARE
1529+
SET age.enable_containment = on;
1530+
1531+
PREPARE issue_1964_vertex_on(agtype) AS
1532+
SELECT * FROM cypher('issue_1964',
1533+
$$MATCH (n $props) RETURN n $$, $1) AS (p agtype);
1534+
EXECUTE issue_1964_vertex_on('{"props": {"name": "Alice"}}');
1535+
DEALLOCATE issue_1964_vertex_on;
14941536

14951537
--
14961538
-- Clean up
@@ -1501,6 +1543,7 @@ SELECT drop_graph('test_enable_containment', true);
15011543
SELECT drop_graph('issue_945', true);
15021544
SELECT drop_graph('issue_1399', true);
15031545
SELECT drop_graph('issue_1393', true);
1546+
SELECT drop_graph('issue_1964', true);
15041547

15051548
--
15061549
-- End

src/backend/parser/cypher_clause.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4261,6 +4261,17 @@ static Node *create_property_constraints(cypher_parsestate *cpstate,
42614261
}
42624262
else
42634263
{
4264+
/*
4265+
* Map decomposition into individual index lookups requires known
4266+
* keys at parse time. When the property constraint is a parameter
4267+
* (cypher_param), the keys are not available until execution, so
4268+
* fall back to the containment operator.
4269+
*/
4270+
if (is_ag_node(property_constraints, cypher_param))
4271+
{
4272+
return (Node *)make_op(pstate, list_make1(makeString("@>")),
4273+
prop_expr, const_expr, last_srf, -1);
4274+
}
42644275
return (Node *)transform_map_to_ind(
42654276
cpstate, entity, (cypher_map *)property_constraints);
42664277
}
@@ -4690,7 +4701,10 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
46904701
-1);
46914702
}
46924703

4693-
((cypher_map*)node->props)->keep_null = true;
4704+
if (is_ag_node(node->props, cypher_map))
4705+
{
4706+
((cypher_map*)node->props)->keep_null = true;
4707+
}
46944708
n = create_property_constraints(cpstate, entity, node->props,
46954709
prop_expr);
46964710

@@ -4819,7 +4833,10 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
48194833
false, -1);
48204834
}
48214835

4822-
((cypher_map*)rel->props)->keep_null = true;
4836+
if (is_ag_node(rel->props, cypher_map))
4837+
{
4838+
((cypher_map*)rel->props)->keep_null = true;
4839+
}
48234840
r = create_property_constraints(cpstate, entity, rel->props,
48244841
prop_expr);
48254842

0 commit comments

Comments
 (0)