Skip to content
Permalink
Browse files
Fix bug in openCypher CREATE AGE2-337
This fixes a bug in the openCypher execution logic for the CREATE
command. See Jira ticket AGE2-337 for more details.

This bug became obvious when using the openCypher CREATE command
as part of an INSERT INTO command. There may be other similar
commands that were affected that this ticket might resolve. This
is due to how the information being passed from the openCypher
SELECT command is passed back into other commands.

This fix involves saving and restoring the result relation info for
the current target node state. This is done in both the create_vertex
and create_edge functions.

The previous logic just overwrote the value. However, when tuples
were passed back, say to INSERT, this modified value did not align
with what the value actually was. This caused crashes as the data
had the wrong descriptor. It may have also cause silent bugs.

Added regression tests for INSERT INTO (...)
  • Loading branch information
jrgemignani committed Aug 18, 2021
1 parent c681989 commit 7c990fa71b01e3ea1456938673237a60cd244994
Showing 3 changed files with 63 additions and 15 deletions.
@@ -578,12 +578,26 @@ $$) as (a agtype);
ERROR: label existing_elabel is for edges, not vertices
LINE 2: CREATE (a:existing_elabel { id: 5})
^
--
-- check the cypher CREATE clause inside an INSERT INTO
--
CREATE TABLE simple_path (u agtype, e agtype, v agtype);
INSERT INTO simple_path(SELECT * FROM cypher('cypher_create',
$$CREATE (u)-[e:knows]->(v) return u, e, v
$$) AS (u agtype, e agtype, v agtype));
SELECT count(*) FROM simple_path;
count
-------
1
(1 row)

--
-- Clean up
--
DROP TABLE simple_path;
DROP FUNCTION create_test;
SELECT drop_graph('cypher_create', true);
NOTICE: drop cascades to 11 other objects
NOTICE: drop cascades to 12 other objects
DETAIL: drop cascades to table cypher_create._ag_label_vertex
drop cascades to table cypher_create._ag_label_edge
drop cascades to table cypher_create.v
@@ -595,6 +609,7 @@ drop cascades to table cypher_create.b_var
drop cascades to table cypher_create.new_vertex
drop cascades to table cypher_create.existing_vlabel
drop cascades to table cypher_create.existing_elabel
drop cascades to table cypher_create.knows
NOTICE: graph "cypher_create" has been dropped
drop_graph
------------
@@ -273,9 +273,20 @@ SELECT * FROM cypher('cypher_create', $$
RETURN a.id
$$) as (a agtype);

--
-- check the cypher CREATE clause inside an INSERT INTO
--
CREATE TABLE simple_path (u agtype, e agtype, v agtype);

INSERT INTO simple_path(SELECT * FROM cypher('cypher_create',
$$CREATE (u)-[e:knows]->(v) return u, e, v
$$) AS (u agtype, e agtype, v agtype));

SELECT count(*) FROM simple_path;
--
-- Clean up
--
DROP TABLE simple_path;
DROP FUNCTION create_test;
SELECT drop_graph('cypher_create', true);

@@ -324,6 +324,7 @@ static void create_edge(cypher_create_custom_scan_state *css,
EState *estate = css->css.ss.ps.state;
ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
ResultRelInfo *resultRelInfo = node->resultRelInfo;
ResultRelInfo *old_estate_es_result_relation_info = NULL;
TupleTableSlot *elemTupleSlot = node->elemTupleSlot;
TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple;
Datum id;
@@ -368,6 +369,10 @@ static void create_edge(cypher_create_custom_scan_state *css,
*
* Note: This obliterates what was their previously
*/

/* save the old result relation info */
old_estate_es_result_relation_info = estate->es_result_relation_info;

estate->es_result_relation_info = resultRelInfo;

ExecClearTuple(elemTupleSlot);
@@ -394,6 +399,9 @@ static void create_edge(cypher_create_custom_scan_state *css,
// Insert the new edge
insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);

/* restore the old result relation info */
estate->es_result_relation_info = old_estate_es_result_relation_info;

/*
* When the edge is used by clauses higher in the execution tree
* we need to create an edge datum. When the edge is a variable,
@@ -447,12 +455,18 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
*/
if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags))
{
ResultRelInfo *old_estate_es_result_relation_info = NULL;

/*
* Set estate's result relation to the vertex's result
* relation.
*
* Note: This obliterates what was their previously
*/

/* save the old result relation info */
old_estate_es_result_relation_info = estate->es_result_relation_info;

estate->es_result_relation_info = resultRelInfo;

ExecClearTuple(elemTupleSlot);
@@ -471,6 +485,9 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
// Insert the new vertex
insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);

/* restore the old result relation info */
estate->es_result_relation_info = old_estate_es_result_relation_info;

/*
* When the vertex is used by clauses higher in the execution tree
* we need to create a vertex datum. When the vertex is a variable,
@@ -494,12 +511,13 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
// append to the path list
if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
{
css->path_values = lappend(css->path_values, DatumGetPointer(result));
css->path_values = lappend(css->path_values,
DatumGetPointer(result));
}

/*
* Put the vertex in the correct spot in the scantuple, so parent execution
* nodes can reference the newly created variable.
* Put the vertex in the correct spot in the scantuple, so parent
* execution nodes can reference the newly created variable.
*/
if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags))
{
@@ -537,27 +555,30 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
id = GRAPHID_GET_DATUM(id_value->val.int_value);

/*
* Its possible the variable has already been deleted. There are two ways
* this can happen. One is the query explicitly deleted the variable, the
* is_deleted flag will catch that. However, it is possible the user deleted
* the vertex using another variable name. We need to scan the table to find
* the vertex's current status relative to this CREATE clause. If the variable
* was initially created in this clause, we can skip this check, because the
* transaction system guarantees that nothing can happen to that tuple, as
* far as we are concerned with at this time.
* Its possible the variable has already been deleted. There are two
* ways this can happen. One is the query explicitly deleted the
* variable, the is_deleted flag will catch that. However, it is
* possible the user deleted the vertex using another variable name. We
* need to scan the table to find the vertex's current status relative
* to this CREATE clause. If the variable was initially created in this
* clause, we can skip this check, because the transaction system
* guarantees that nothing can happen to that tuple, as far as we are
* concerned with at this time.
*/
if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags))
{
if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id)))
ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("vertex assigned to variable %s was deleted", node->variable_name)));
errmsg("vertex assigned to variable %s was deleted",
node->variable_name)));
}

if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
{
Datum vertex = scanTupleSlot->tts_values[node->tuple_position - 1];
css->path_values = lappend(css->path_values, DatumGetPointer(vertex));
css->path_values = lappend(css->path_values,
DatumGetPointer(vertex));
}
}

@@ -616,7 +637,8 @@ static bool entity_exists(EState *estate, Oid graph_oid, graphid id)
* constraints have not been violated.
*/
static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo,
TupleTableSlot *elemTupleSlot, EState *estate)
TupleTableSlot *elemTupleSlot,
EState *estate)
{
HeapTuple tuple;

0 comments on commit 7c990fa

Please sign in to comment.