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
40 changes: 40 additions & 0 deletions regress/expected/cypher_create.out
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,46 @@ EXECUTE p_2('{"var_name": "Hello Prepared Statements 2"}');
{"id": 2533274790395908, "label": "new_vertex", "properties": {"key": "Hello Prepared Statements 2"}}::vertex
(1 row)

-- prepared statement with scalar parameters used in MATCH and CREATE EDGE.
-- Regression test: transform_cypher_param() previously called the VARIADIC
-- agtype_access_operator without building the agtype[] ArrayExpr argument
-- and without setting funcvariadic = true. Under the extended query
-- protocol this caused agtype_access_operator to read past its argument
-- at execution time, corrupting error messages with random bytes and
-- producing client-side UnicodeDecodeError / invalid-start-byte errors.
PREPARE p_create_edge(agtype) AS
SELECT * FROM cypher('cypher_create', $$
MATCH (source:v {id: $src_id}), (target:v {id: $tgt_id})
CREATE (source)-[r:e {id: $edge_id}]->(target)
RETURN r
$$, $1) AS (r agtype);
-- seed vertices to match against
SELECT * FROM cypher('cypher_create', $$
CREATE (:v {id: 'a'}), (:v {id: 'b'})
$$) AS (a agtype);
a
---
(0 rows)

EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge1"}');
r
-------------------------------------------------------------------------------------------------------------------------------------
{"id": 1125899906842635, "label": "e", "end_id": 844424930131990, "start_id": 844424930131989, "properties": {"id": "edge1"}}::edge
(1 row)

EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge2"}');
r
-------------------------------------------------------------------------------------------------------------------------------------
{"id": 1125899906842636, "label": "e", "end_id": 844424930131990, "start_id": 844424930131989, "properties": {"id": "edge2"}}::edge
(1 row)

EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge3"}');
r
-------------------------------------------------------------------------------------------------------------------------------------
{"id": 1125899906842637, "label": "e", "end_id": 844424930131990, "start_id": 844424930131989, "properties": {"id": "edge3"}}::edge
(1 row)

DEALLOCATE p_create_edge;
-- pl/pgsql
CREATE FUNCTION create_test()
RETURNS TABLE(vertex agtype)
Expand Down
25 changes: 25 additions & 0 deletions regress/sql/cypher_create.sql
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,31 @@ PREPARE p_2 AS SELECT * FROM cypher('cypher_create', $$CREATE (v:new_vertex {key
EXECUTE p_2('{"var_name": "Hello Prepared Statements"}');
EXECUTE p_2('{"var_name": "Hello Prepared Statements 2"}');

-- prepared statement with scalar parameters used in MATCH and CREATE EDGE.
-- Regression test: transform_cypher_param() previously called the VARIADIC
-- agtype_access_operator without building the agtype[] ArrayExpr argument
-- and without setting funcvariadic = true. Under the extended query
-- protocol this caused agtype_access_operator to read past its argument
-- at execution time, corrupting error messages with random bytes and
-- producing client-side UnicodeDecodeError / invalid-start-byte errors.
PREPARE p_create_edge(agtype) AS
SELECT * FROM cypher('cypher_create', $$
MATCH (source:v {id: $src_id}), (target:v {id: $tgt_id})
CREATE (source)-[r:e {id: $edge_id}]->(target)
RETURN r
$$, $1) AS (r agtype);

-- seed vertices to match against
SELECT * FROM cypher('cypher_create', $$
CREATE (:v {id: 'a'}), (:v {id: 'b'})
$$) AS (a agtype);

EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge1"}');
EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge2"}');
EXECUTE p_create_edge('{"src_id": "a", "tgt_id": "b", "edge_id": "edge3"}');

DEALLOCATE p_create_edge;

-- pl/pgsql
CREATE FUNCTION create_test()
RETURNS TABLE(vertex agtype)
Expand Down
32 changes: 30 additions & 2 deletions src/backend/parser/cypher_expr.c
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,7 @@ static Node *transform_cypher_param(cypher_parsestate *cpstate,
cypher_param *cp)
{
ParseState *pstate = (ParseState *)cpstate;
ArrayExpr *newa;
Const *const_str;
FuncExpr *func_expr;
Oid func_access_oid;
Expand All @@ -867,15 +868,42 @@ static Node *transform_cypher_param(cypher_parsestate *cpstate,
func_access_oid = get_ag_func_oid("agtype_access_operator", 1,
AGTYPEARRAYOID);

/*
* agtype_access_operator is declared VARIADIC agtype[] and expects a
* single agtype[] ArrayExpr with funcvariadic = true. Every other call
* site in this file (see transform_cypher_map_projection,
* transform_cypher_indirection) assembles its arguments via
* make_agtype_array_expr() and sets funcvariadic; only this function was
* passing the Param and the key Const as separate list elements directly.
*
* With the Simple Query protocol PostgreSQL implicitly forms the variadic
* array from separate arguments at execution time, masking the bug. But
* under the Extended Query protocol (prepared statements /
* Parse-Bind-Execute) the executor does not perform that implicit
* assembly, so agtype_access_operator read past its single agtype
* argument treating adjacent memory as additional array elements. The
* resulting out-of-bounds read surfaced as corrupted relation names in
* error responses (random bytes mixed with fragments of property values
* such as "November", "customer", "communication"; control bytes such as
* 0x01), which client drivers then failed to UTF-8 decode:
*
* UnicodeDecodeError: 'utf-8' ... invalid start byte
*
* The fix matches every other call site: pack the arguments into an
* agtype[] ArrayExpr and set funcvariadic = true.
*/
args = lappend(args, copyObject(cpstate->params));

const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1,
string_to_agtype(cp->name), false, false);

args = lappend(args, const_str);

func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid,
InvalidOid, COERCE_EXPLICIT_CALL);
newa = make_agtype_array_expr(args);

func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa),
InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL);
func_expr->funcvariadic = true;
func_expr->location = cp->location;

return (Node *)func_expr;
Expand Down