in src/backend/executor/cypher_merge.c [1261:1495]
static void merge_edge(cypher_merge_custom_scan_state *css,
cypher_target_node *node, Datum prev_vertex_id,
ListCell *next, List *list,
path_entry **path_array, int path_index,
bool should_insert)
{
bool isNull;
EState *estate = css->css.ss.ps.state;
ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
ResultRelInfo *resultRelInfo = node->resultRelInfo;
ResultRelInfo **old_estate_es_result_relations = NULL;
TupleTableSlot *elemTupleSlot = node->elemTupleSlot;
Datum id;
Datum start_id, end_id, next_vertex_id;
List *prev_path = css->path_values;
Datum prop;
Assert(node->type == LABEL_KIND_EDGE);
Assert(lfirst(next) != NULL);
/*
* Create the next vertex before creating the edge. We need the
* next vertex's id.
*/
css->path_values = NIL;
next_vertex_id = merge_vertex(css, lfirst(next), lnext(list, next), list,
path_array, path_index+1, should_insert);
/*
* Set the start and end vertex ids
*/
if (node->dir == CYPHER_REL_DIR_RIGHT || node->dir == CYPHER_REL_DIR_NONE)
{
/* create pattern (prev_vertex)-[edge]->(next_vertex) */
start_id = prev_vertex_id;
end_id = next_vertex_id;
}
else if (node->dir == CYPHER_REL_DIR_LEFT)
{
/* create pattern (prev_vertex)<-[edge]-(next_vertex) */
start_id = next_vertex_id;
end_id = prev_vertex_id;
}
else
{
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("edge direction must be specified in a MERGE clause")));
}
/*
* 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_relations = estate->es_result_relations;
estate->es_result_relations = &resultRelInfo;
ExecClearTuple(elemTupleSlot);
/* if we not are going to insert, we need our structure pointers */
if (should_insert == false &&
(path_array == NULL || path_array[path_index] == NULL))
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid input parameter combination")));
}
/*
* If we shouldn't insert the edge, we need to retrieve the entire edge from
* the storage structure.
*/
if (should_insert == false &&
path_array != NULL &&
path_array[path_index] != NULL)
{
id = path_array[path_index]->id;
isNull = path_array[path_index]->id_isNull;
start_id = path_array[path_index]->start_id;
end_id = path_array[path_index]->end_id;
}
/*
* Otherwise, we need to get the edge's ID and store its unique values if
* the storage structure exists
*/
else if (should_insert == true)
{
/* get the next graphid for this edge */
id = ExecEvalExpr(node->id_expr_state, econtext, &isNull);
if (path_array != NULL && path_array[path_index] != NULL)
{
/* store it */
path_array[path_index]->id = id;
path_array[path_index]->id_isNull = isNull;
path_array[path_index]->start_id = start_id;
path_array[path_index]->end_id = end_id;
}
}
else
{
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("invalid input parameter combination")));
}
/* put the id values into the tuple slot */
elemTupleSlot->tts_values[edge_tuple_id] = id;
elemTupleSlot->tts_isnull[edge_tuple_id] = isNull;
/* Graph id for the starting vertex */
elemTupleSlot->tts_values[edge_tuple_start_id] = start_id;
elemTupleSlot->tts_isnull[edge_tuple_start_id] = false;
/* Graph id for the ending vertex */
elemTupleSlot->tts_values[edge_tuple_end_id] = end_id;
elemTupleSlot->tts_isnull[edge_tuple_end_id] = false;
/*
* Retrieve the properties and isNull values if the storage structure
* exists.
*/
if (path_array != NULL && path_array[path_index] != NULL)
{
prop = path_array[path_index]->prop;
isNull = path_array[path_index]->prop_isNull;
}
/* otherwise, get them normally */
else
{
/* get the properties for this edge */
prop = ExecEvalExpr(node->prop_expr_state, econtext, &isNull);
}
/* store the properties in the tuple slot */
elemTupleSlot->tts_values[edge_tuple_properties] = prop;
elemTupleSlot->tts_isnull[edge_tuple_properties] = isNull;
/*
* Insert the new edge.
*
* Depending on the currentCommandId, we need to do this one of two
* different ways -
*
* 1) If they are equal, the currentCommandId hasn't been used for an
* update, or it hasn't been incremented after being used. In either
* case, we need to use the current one and then increment it so that
* the following commands will have visibility of this update. Note,
* it isn't our job to update the currentCommandId first and then do
* this check.
*
* 2) If they are not equal, the currentCommandId has been used and/or
* updated. In this case, we can't use it. Otherwise our update won't
* be visible to anything that follows, until the currentCommandId is
* updated again. Remember, visibility is, greater than but not equal
* to, the currentCommandID used for the update. So, in this case we
* need to use the original currentCommandId when begin_cypher_merge
* was initiated as everything under this instance of merge needs to
* be based off of that initial currentCommandId. This allows the
* following command to see the updates generated by this instance of
* merge.
*/
if (should_insert &&
css->base_currentCommandId == GetCurrentCommandId(false))
{
insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);
/*
* Increment the currentCommandId since we processed an update. We
* don't want to do this outside of this block because we don't want
* to inadvertently or unnecessarily update the commandCounterId of
* another command.
*/
CommandCounterIncrement();
}
else if (should_insert)
{
insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate,
css->base_currentCommandId);
}
/* restore the old result relation info */
estate->es_result_relations = old_estate_es_result_relations;
/*
* 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,
* add to the scantuple slot. When the edge is part of a path
* variable, add to the list.
*/
if (CYPHER_TARGET_NODE_OUTPUT(node->flags))
{
Datum result;
result = make_edge(id, start_id, end_id,
CStringGetDatum(node->label_name), prop);
/* add the Datum to the list of entities for creating the path variable */
if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
{
prev_path = lappend(prev_path, DatumGetPointer(result));
css->path_values = list_concat(prev_path, css->path_values);
}
/* Add the entity to the TupleTableSlot if necessary */
if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags))
{
TupleTableSlot *scantuple = econtext->ecxt_scantuple;
bool debug_flag = false;
int tuple_position = node->tuple_position - 1;
/*
* We need to make sure that the tuple_position is within the
* boundaries of the tuple's number of attributes. Otherwise, it
* will corrupt memory. The cases where it doesn't fall within are
* usually due to a variable that is specified but there isn't a
* RETURN clause. In these cases we just don't bother to store the
* value.
*/
if (!debug_flag &&
(tuple_position < scantuple->tts_tupleDescriptor->natts ||
scantuple->tts_tupleDescriptor->natts != 1))
{
/* store the result */
scantuple->tts_values[tuple_position] = result;
scantuple->tts_isnull[tuple_position] = false;
}
}
}
}