static void merge_edge()

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;
            }
        }
    }
}