You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@age.apache.org by jo...@apache.org on 2021/04/20 23:26:28 UTC
[incubator-age] branch master updated: Refactor Create Clause
This is an automated email from the ASF dual-hosted git repository.
joshinnis pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-age.git
The following commit(s) were added to refs/heads/master by this push:
new 1f15772 Refactor Create Clause
1f15772 is described below
commit 1f15772693be43618854b9c6e80e3fada7f682df
Author: Josh Innis <Jo...@gmail.com>
AuthorDate: Mon Apr 19 09:46:15 2021 -0700
Refactor Create Clause
Simplified the logic for getting the graphid of entities
where the entity already exists.
Replaced the cypher_target_node field from transform entity
with declared_in_current_clause.
Renamed some AttrNumber in the cypher_target_node and
cypher_create_path to reflect that they are AttrNumbers.
Altered the id fields in cypher_target_node to simplify the
logic.
Added documentation to the transform_entity data structure.
Added documentation to the cypher_target_node function.
Altered the flow of logic when the CREATE clause must process
all children tuples in one call of exec_cypher_create to be like
the delete clause logic.
---
src/backend/executor/cypher_create.c | 220 ++++++++++++++---------------
src/backend/nodes/cypher_copyfuncs.c | 10 +-
src/backend/nodes/cypher_outfuncs.c | 10 +-
src/backend/nodes/cypher_readfuncs.c | 10 +-
src/backend/parser/cypher_clause.c | 259 ++++++++++++++++-------------------
src/include/nodes/cypher_nodes.h | 72 +++++++---
6 files changed, 299 insertions(+), 282 deletions(-)
diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c
index a5769fe..c7aeb30 100644
--- a/src/backend/executor/cypher_create.c
+++ b/src/backend/executor/cypher_create.c
@@ -54,7 +54,6 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
static HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo,
TupleTableSlot *elemTupleSlot, EState *estate);
static void process_pattern(cypher_create_custom_scan_state *css);
-static void process_all_tuples(CustomScanState *node, EState *estate);
static bool entity_exists(EState *estate, Oid graph_oid, graphid id);
const CustomExecMethods cypher_create_exec_methods = {CREATE_SCAN_STATE_NAME,
@@ -104,7 +103,6 @@ static void begin_cypher_create(CustomScanState *node, EState *estate,
{
cypher_target_node *cypher_node =
(cypher_target_node *)lfirst(lc2);
- ListCell *lc_expr;
Relation rel;
if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags))
@@ -114,7 +112,7 @@ static void begin_cypher_create(CustomScanState *node, EState *estate,
rel = heap_open(cypher_node->relid, RowExclusiveLock);
// Initialize resultRelInfo for the vertex
- cypher_node->resultRelInfo = palloc(sizeof(ResultRelInfo));
+ cypher_node->resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(cypher_node->resultRelInfo, rel,
list_length(estate->es_range_table), NULL,
estate->es_instrument);
@@ -127,14 +125,10 @@ static void begin_cypher_create(CustomScanState *node, EState *estate,
estate,
RelationGetDescr(cypher_node->resultRelInfo->ri_RelationDesc));
- // setup expr states for the relation's target list
- foreach (lc_expr, cypher_node->targetList)
+ if (cypher_node->id_expr != NULL)
{
- TargetEntry *te = lfirst(lc_expr);
-
- cypher_node->expr_states =
- lappend(cypher_node->expr_states,
- ExecInitExpr(te->expr, (PlanState *)node));
+ cypher_node->id_expr_state =
+ ExecInitExpr(cypher_node->id_expr, (PlanState *)node);
}
}
}
@@ -179,7 +173,7 @@ static void process_pattern(cypher_create_custom_scan_state *css)
* in the vertex/edge creation, create a path datum, and add to the
* scantuple slot.
*/
- if (path->tuple_position > 0)
+ if (path->path_attr_num != InvalidAttrNumber)
{
TupleTableSlot *scantuple;
PlanState *ps;
@@ -190,83 +184,69 @@ static void process_pattern(cypher_create_custom_scan_state *css)
result = make_path(css->path_values);
- scantuple->tts_values[path->tuple_position - 1] = result;
- scantuple->tts_isnull[path->tuple_position - 1] = false;
+ scantuple->tts_values[path->path_attr_num - 1] = result;
+ scantuple->tts_isnull[path->path_attr_num - 1] = false;
}
css->path_values = NIL;
}
}
-/*
- * When the CREATE clause is the last cypher clause, consume all input from the
- * previous clause(s) in the first call of exec_cypher_create.
- */
-static void process_all_tuples(CustomScanState *node, EState *estate)
-{
- cypher_create_custom_scan_state *css =
- (cypher_create_custom_scan_state *)node;
- TupleTableSlot *slot;
-
- do
- {
- process_pattern(css);
-
- Decrement_Estate_CommandId(estate);
- slot = ExecProcNode(node->ss.ps.lefttree);
- Increment_Estate_CommandId(estate);
- } while (!TupIsNull(slot));
-}
-
static TupleTableSlot *exec_cypher_create(CustomScanState *node)
{
cypher_create_custom_scan_state *css =
(cypher_create_custom_scan_state *)node;
- ResultRelInfo *saved_resultRelInfo;
EState *estate = css->css.ss.ps.state;
ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
TupleTableSlot *slot;
- MemoryContext old_mcxt;
-
- saved_resultRelInfo = estate->es_result_relation_info;
-
- //Process the subtree first
- Decrement_Estate_CommandId(estate);
- slot = ExecProcNode(node->ss.ps.lefttree);
- Increment_Estate_CommandId(estate);
-
- css->slot = slot;
-
- econtext->ecxt_scantuple =
- node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
-
- if (TupIsNull(slot))
- return NULL;
-
- old_mcxt = MemoryContextSwitchTo(econtext->ecxt_scantuple->tts_mcxt);
if (CYPHER_CLAUSE_IS_TERMINAL(css->flags))
{
- process_all_tuples(node, estate);
+ /*
+ * If the CREATE clause was the final cypher clause written
+ * then we aren't returning anything from this result node.
+ * So the exec_cypher_create function will only be called once.
+ * Therefore we will process all tuples from the subtree at once.
+ */
+ while(true)
+ {
+ //Process the subtree first
+ Decrement_Estate_CommandId(estate)
+ slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate)
+
+ if (TupIsNull(slot))
+ break;
- MemoryContextSwitchTo(old_mcxt);
+ // setup the scantuple that the process_pattern needs
+ econtext->ecxt_scantuple =
+ node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
- estate->es_result_relation_info = saved_resultRelInfo;
+ css->tuple_info = NIL;
+
+ process_pattern(css);
+ }
return NULL;
}
else
{
- process_pattern(css);
+ //Process the subtree first
+ Decrement_Estate_CommandId(estate)
+ slot = ExecProcNode(node->ss.ps.lefttree);
+ Increment_Estate_CommandId(estate)
- MemoryContextSwitchTo(old_mcxt);
+ if (TupIsNull(slot))
+ return NULL;
- estate->es_result_relation_info = saved_resultRelInfo;
+ // setup the scantuple that the process_delete_list needs
+ econtext->ecxt_scantuple =
+ node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple;
+
+ css->tuple_info = NIL;
+
+ process_pattern(css);
- /*
- * Now that process_pattern has filled in the missing values,
- * rerun the projection information.
- */
econtext->ecxt_scantuple =
ExecProject(node->ss.ps.lefttree->ps_ProjInfo);
@@ -349,7 +329,6 @@ static void create_edge(cypher_create_custom_scan_state *css,
{
bool isNull;
EState *estate = css->css.ss.ps.state;
- ExprState *es;
ExprContext *econtext = css->css.ss.ps.ps_ExprContext;
ResultRelInfo *resultRelInfo = node->resultRelInfo;
TupleTableSlot *elemTupleSlot = node->elemTupleSlot;
@@ -378,12 +357,18 @@ static void create_edge(cypher_create_custom_scan_state *css,
start_id = prev_vertex_id;
end_id = next_vertex_id;
}
- else
+ 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 CREATE clause")));
+ }
/*
* Set estate's result relation to the vertex's result
@@ -396,8 +381,7 @@ static void create_edge(cypher_create_custom_scan_state *css,
ExecClearTuple(elemTupleSlot);
// Graph Id for the edge
- es = linitial(node->expr_states);
- id = ExecEvalExpr(es, econtext, &isNull);
+ id = ExecEvalExpr(node->id_expr_state, econtext, &isNull);
elemTupleSlot->tts_values[edge_tuple_id] = id;
elemTupleSlot->tts_isnull[edge_tuple_id] = isNull;
@@ -411,9 +395,9 @@ static void create_edge(cypher_create_custom_scan_state *css,
// Edge's properties map
elemTupleSlot->tts_values[edge_tuple_properties] =
- scanTupleSlot->tts_values[node->prop_var_no];
+ scanTupleSlot->tts_values[node->prop_attr_num];
elemTupleSlot->tts_isnull[edge_tuple_properties] =
- scanTupleSlot->tts_isnull[node->prop_var_no];
+ scanTupleSlot->tts_isnull[node->prop_attr_num];
// Insert the new edge
tuple = insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);
@@ -435,7 +419,7 @@ static void create_edge(cypher_create_custom_scan_state *css,
result = make_edge(
id, start_id, end_id, CStringGetDatum(node->label_name),
- PointerGetDatum(scanTupleSlot->tts_values[node->prop_var_no]));
+ PointerGetDatum(scanTupleSlot->tts_values[node->prop_attr_num]));
if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
{
@@ -467,11 +451,13 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
Assert(node->type == LABEL_KIND_VERTEX);
+ /*
+ * Vertices in a path might already exists. If they do get the id
+ * to pass to the edges before and after it. Otherwise, insert the
+ * new vertex into it's table and then pass the id along.
+ */
if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags))
{
- PlanState *ps = css->css.ss.ps.lefttree;
- TupleTableSlot *scantuple = ps->ps_ExprContext->ecxt_scantuple;
- ExprState *id_es = linitial(node->expr_states);
HeapTuple tuple;
/*
@@ -484,21 +470,26 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
ExecClearTuple(elemTupleSlot);
- id = ExecEvalExpr(id_es, econtext, &isNull);
+ // get the next graphid for this vertex.
+ id = ExecEvalExpr(node->id_expr_state, econtext, &isNull);
elemTupleSlot->tts_values[vertex_tuple_id] = id;
elemTupleSlot->tts_isnull[vertex_tuple_id] = isNull;
- scantuple->tts_values[node->id_var_no - 1] = id;
- scantuple->tts_isnull[node->id_var_no - 1] = isNull;
-
+ // get the properties for this vertex
elemTupleSlot->tts_values[vertex_tuple_properties] =
- scanTupleSlot->tts_values[node->prop_var_no];
+ scanTupleSlot->tts_values[node->prop_attr_num];
elemTupleSlot->tts_isnull[vertex_tuple_properties] =
- scanTupleSlot->tts_isnull[node->prop_var_no];
+ scanTupleSlot->tts_isnull[node->prop_attr_num];
// Insert the new vertex
tuple = insert_entity_tuple(resultRelInfo, elemTupleSlot, estate);
+ /*
+ * If this vertex is a variable store the newly created tuple in
+ * the CustomScanState. This will tell future clauses what the
+ * tuple is for this variable, which is needed if the query wants
+ * to update this tuple.
+ */
if (node->variable_name != NULL)
{
clause_tuple_information *tuple_info;
@@ -526,13 +517,21 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
ps = css->css.ss.ps.lefttree;
scantuple = ps->ps_ExprContext->ecxt_scantuple;
+ // make the vertex agtype
result = make_vertex(
id, CStringGetDatum(node->label_name),
- PointerGetDatum(scanTupleSlot->tts_values[node->prop_var_no]));
+ PointerGetDatum(scanTupleSlot->tts_values[node->prop_attr_num]));
+ // append to the path list
if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
+ {
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.
+ */
if (CYPHER_TARGET_NODE_IS_VARIABLE(node->flags))
{
scantuple->tts_values[node->tuple_position - 1] = result;
@@ -542,30 +541,46 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
}
else
{
- if (CYPHER_TARGET_NODE_ID_IS_AGTYPE(node->flags))
- {
- agtype *a;
- agtype_value *v;
- bool is_deleted = false;
+ agtype *a;
+ agtype_value *v;
+ agtype_value *id_value;
+ TupleTableSlot *scantuple;
+ PlanState *ps;
- a = (agtype *)scanTupleSlot->tts_values[node->id_var_no];
+ ps = css->css.ss.ps.lefttree;
+ scantuple = ps->ps_ExprContext->ecxt_scantuple;
- v = get_ith_agtype_value_from_container(&a->root, 0);
+ // get the vertex agtype in the scanTupleSlot
+ a = DATUM_GET_AGTYPE_P(scantuple->tts_values[node->tuple_position - 1]);
- if (v->type != AGTV_INTEGER)
- ereport(ERROR,
- (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("id agtype must resolve to a graphid")));
+ // Convert to an agtype value
+ v = get_ith_agtype_value_from_container(&a->root, 0);
- id = GRAPHID_GET_DATUM(v->val.int_value);
+ if (v->type != AGTV_VERTEX)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("agtype must resolve to a vertex")));
+
+ // extract the id agtype field
+ id_value = get_agtype_value_object_value(v, "id");
+
+ // extract the graphid and cast to a Datum
+ 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.
+ */
+ if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags))
+ {
+ bool is_deleted = false;
- /*
- * 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.
- */
get_heap_tuple(&css->css, node->variable_name, &is_deleted);
if (is_deleted || !entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id)))
@@ -573,15 +588,6 @@ static Datum create_vertex(cypher_create_custom_scan_state *css,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("vertex assigned to variable %s was deleted", node->variable_name)));
}
- else if (CYPHER_TARGET_NODE_ID_IS_GRAPHID(node->flags))
- {
- id = scanTupleSlot->tts_values[node->id_var_no - 1];
- }
- else
- {
- ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("id agtype must resolve to a graphid")));
- }
if (CYPHER_TARGET_NODE_IN_PATH(node->flags))
{
diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c
index 59632ac..e1546e9 100644
--- a/src/backend/nodes/cypher_copyfuncs.c
+++ b/src/backend/nodes/cypher_copyfuncs.c
@@ -87,7 +87,7 @@ void copy_cypher_create_path(ExtensibleNode *newnode, const ExtensibleNode *from
{
COPY_LOCALS(cypher_create_path);
- COPY_SCALAR_FIELD(tuple_position);
+ COPY_SCALAR_FIELD(path_attr_num);
COPY_NODE_FIELD(target_nodes);
}
@@ -100,19 +100,17 @@ void copy_cypher_target_node(ExtensibleNode *newnode, const ExtensibleNode *from
COPY_SCALAR_FIELD(type);
COPY_SCALAR_FIELD(flags);
COPY_SCALAR_FIELD(dir);
- COPY_SCALAR_FIELD(id_var_no);
- COPY_SCALAR_FIELD(prop_var_no);
+ COPY_SCALAR_FIELD(prop_attr_num);
COPY_SCALAR_FIELD(relid);
COPY_SCALAR_FIELD(tuple_position);
COPY_STRING_FIELD(label_name);
COPY_STRING_FIELD(variable_name);
+ COPY_NODE_FIELD(id_expr);
+ COPY_NODE_FIELD(id_expr_state);
COPY_NODE_FIELD(resultRelInfo);
COPY_NODE_FIELD(elemTupleSlot);
- COPY_NODE_FIELD(te);
- COPY_NODE_FIELD(targetList);
- COPY_NODE_FIELD(expr_states);
}
// copy function for cypher_update_information
diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c
index d0ef9e8..fb5abd8 100644
--- a/src/backend/nodes/cypher_outfuncs.c
+++ b/src/backend/nodes/cypher_outfuncs.c
@@ -291,7 +291,7 @@ void out_cypher_create_path(StringInfo str, const ExtensibleNode *node)
DEFINE_AG_NODE(cypher_create_path);
WRITE_NODE_FIELD(target_nodes);
- WRITE_INT32_FIELD(tuple_position);
+ WRITE_INT32_FIELD(path_attr_num);
}
// serialization function for the cypher_target_node ExtensibleNode.
@@ -302,11 +302,9 @@ void out_cypher_target_node(StringInfo str, const ExtensibleNode *node)
WRITE_CHAR_FIELD(type);
WRITE_INT32_FIELD(flags);
WRITE_ENUM_FIELD(dir, cypher_rel_dir);
- WRITE_INT32_FIELD(id_var_no);
- WRITE_INT32_FIELD(prop_var_no);
- WRITE_NODE_FIELD(targetList);
- WRITE_NODE_FIELD(te);
- WRITE_NODE_FIELD(expr_states);
+ WRITE_NODE_FIELD(id_expr);
+ WRITE_NODE_FIELD(id_expr_state);
+ WRITE_INT32_FIELD(prop_attr_num);
WRITE_NODE_FIELD(resultRelInfo);
WRITE_NODE_FIELD(elemTupleSlot);
WRITE_OID_FIELD(relid);
diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c
index 5486c69..76fe63b 100644
--- a/src/backend/nodes/cypher_readfuncs.c
+++ b/src/backend/nodes/cypher_readfuncs.c
@@ -191,7 +191,7 @@ void read_cypher_create_path(struct ExtensibleNode *node)
READ_LOCALS(cypher_create_path);
READ_NODE_FIELD(target_nodes);
- READ_INT_FIELD(tuple_position);
+ READ_INT_FIELD(path_attr_num);
}
/*
@@ -205,11 +205,9 @@ void read_cypher_target_node(struct ExtensibleNode *node)
READ_CHAR_FIELD(type);
READ_INT_FIELD(flags);
READ_ENUM_FIELD(dir, cypher_rel_dir);
- READ_INT_FIELD(id_var_no);
- READ_INT_FIELD(prop_var_no);
- READ_NODE_FIELD(targetList);
- READ_NODE_FIELD(te);
- READ_NODE_FIELD(expr_states);
+ READ_NODE_FIELD(id_expr);
+ READ_NODE_FIELD(id_expr_state);
+ READ_INT_FIELD(prop_attr_num);
READ_NODE_FIELD(resultRelInfo);
READ_NODE_FIELD(elemTupleSlot);
READ_OID_FIELD(relid);
diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c
index 57d9e05..8b9ebac 100644
--- a/src/backend/parser/cypher_clause.c
+++ b/src/backend/parser/cypher_clause.c
@@ -90,12 +90,46 @@ enum transform_entity_join_side
JOIN_SIDE_RIGHT
};
+/*
+ * In the transformation stage, we need to track
+ * where a variable came from. When moving between
+ * clauses, Postgres parsestate and Query data structures
+ * are insufficient for some of the information we
+ * need.
+ */
typedef struct
{
+ // denotes whether this entity is a vertex or edge
enum transform_entity_type type;
+
+ /*
+ * MATCH clauses are transformed into a select * FROM ... JOIN, etc
+ * We need to know wheter the table that this entity represents is
+ * part of the join tree. If a cypher_node does not meet the conditions
+ * set in INCLUDE_NODE_IN_JOIN_TREE. Then we can skip the node when
+ * constructing our join tree. The entities around this particular entity
+ * need to know this for the join to get properly constructed.
+ */
bool in_join_tree;
+
+ /*
+ * The parse data structure will be transformed into an Expr that represents
+ * the entity. When contructing the join tree, we need to know what it was
+ * turned into. If the entity was originally created in a previous clause,
+ * this will be a Var that we need to reference to extract the id, startid,
+ * endid for the join. If the entity was created in the current clause, then
+ * this will be a FuncExpr that we can reference to get the id, startid, and
+ * endid.
+ */
Expr *expr;
- cypher_target_node *target_node;
+
+ /*
+ * tells each clause whether this variable was
+ * declared by itself or a previous clause.
+ */
+ bool declared_in_current_clause;
+
+ // The parse data structure that we transformed
union
{
cypher_node *node;
@@ -181,8 +215,7 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
FuncCall *id_field, char *label);
static transform_entity *
make_transform_entity(cypher_parsestate *cpstate,
- enum transform_entity_type type, Node *node, Expr *expr,
- cypher_target_node *target_node);
+ enum transform_entity_type type, Node *node, Expr *expr);
static transform_entity *find_variable(cypher_parsestate *cpstate, char *name);
static Node *create_property_constraint_function(cypher_parsestate *cpstate,
transform_entity *entity,
@@ -203,18 +236,11 @@ static cypher_target_node *
transform_create_cypher_new_node(cypher_parsestate *cpstate,
List **target_list, cypher_node *node);
static cypher_target_node *transform_create_cypher_existing_node(
- cypher_parsestate *cpstate, List **target_list, transform_entity *entity,
+ cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause,
cypher_node *node);
static cypher_target_node *
transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list,
cypher_relationship *edge);
-static Expr *cypher_create_id_access_function(cypher_parsestate *cpstate,
- RangeTblEntry *rte,
- enum transform_entity_type type,
- char *name);
-static Node *cypher_create_id_default(cypher_parsestate *cpstate,
- Relation label_relation,
- enum transform_entity_type type);
static Expr *cypher_create_properties(cypher_parsestate *cpstate,
cypher_target_node *rel,
Relation label_relation, Node *props,
@@ -274,6 +300,7 @@ static Index transform_group_clause_expr(List **flatresult,
static List *add_target_to_group_list(cypher_parsestate *cpstate,
TargetEntry *tle, List *grouplist,
List *targetlist, int location);
+static void advance_transform_entities_to_next_clause(List *entities);
/*
* transform a cypher_clause
*/
@@ -1682,8 +1709,7 @@ static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate,
static transform_entity *make_transform_entity(cypher_parsestate *cpstate,
enum transform_entity_type type,
- Node *node, Expr *expr,
- cypher_target_node *target_node)
+ Node *node, Expr *expr)
{
transform_entity *entity;
@@ -1698,11 +1724,10 @@ static transform_entity *make_transform_entity(cypher_parsestate *cpstate,
ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("unknown entity type")));
- entity->target_node = target_node;
+ entity->declared_in_current_clause = true;
entity->expr = expr;
entity->in_join_tree = expr != NULL;
- cpstate->entities = lappend(cpstate->entities, entity);
return entity;
}
@@ -1850,7 +1875,9 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
INCLUDE_NODE_IN_JOIN_TREE(path, node));
entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node,
- expr, NULL);
+ expr);
+
+ cpstate->entities = lappend(cpstate->entities, entity);
if (node->props)
{
@@ -1867,7 +1894,9 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query,
expr = transform_cypher_edge(cpstate, rel, &query->targetList);
entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)rel,
- expr, NULL);
+ expr);
+
+ cpstate->entities = lappend(cpstate->entities, entity);
if (rel->props)
{
@@ -1997,9 +2026,6 @@ transform_match_create_path_variable(cypher_parsestate *cpstate,
* Maps a column name to the a function access name. In others word when
* passed the name for the vertex's id column name, return the function name
* for the vertex's agtype id element, etc.
- *
- * Note: the property colname is not here, because an access function does not
- * currently exist and is not needed.
*/
static char *get_accessor_function_name(enum transform_entity_type type,
char *name)
@@ -2525,6 +2551,8 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list,
cypher_create_path *ccp = make_ag_node(cypher_create_path);
bool in_path = path->var_name != NULL;
+ ccp->path_attr_num = InvalidAttrNumber;
+
foreach (lc, path->path)
{
if (is_ag_node(lfirst(lc), cypher_node))
@@ -2541,7 +2569,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list,
transformed_path = lappend(transformed_path, rel);
entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node,
- NULL, rel);
+ NULL);
cpstate->entities = lappend(cpstate->entities, entity);
}
@@ -2559,7 +2587,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list,
transformed_path = lappend(transformed_path, rel);
entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)edge,
- NULL, rel);
+ NULL);
cpstate->entities = lappend(cpstate->entities, entity);
}
@@ -2572,28 +2600,30 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list,
ccp->target_nodes = transformed_path;
+ /*
+ * If this path a variable, create a placeholder entry that we can fill
+ * in with during the execution phase.
+ */
if (path->var_name)
{
TargetEntry *te;
if (list_length(transformed_path) < 3)
+ {
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("paths consist of alternating vertices and edges."),
parser_errposition(pstate, path->location),
errhint("paths require at least 2 vertices and 1 edge")));
+ }
te = placeholder_target_entry(cpstate, path->var_name);
- ccp->tuple_position = te->resno;
+ ccp->path_attr_num = te->resno;
*target_list = lappend(*target_list, te);
}
- else
- {
- ccp->tuple_position = -1;
- }
return ccp;
}
@@ -2604,18 +2634,18 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list,
{
ParseState *pstate = (ParseState *)cpstate;
cypher_target_node *rel = make_ag_node(cypher_target_node);
- List *targetList = NIL;
- Expr *id, *props;
+ Expr *props;
Relation label_relation;
RangeVar *rv;
RangeTblEntry *rte;
TargetEntry *te;
char *alias;
- int resno;
+ AttrNumber resno;
rel->type = LABEL_KIND_EDGE;
rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT;
rel->label_name = edge->label;
+ rel->resultRelInfo = NULL;
if (edge->name)
{
@@ -2681,14 +2711,9 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list,
rte->requiredPerms = ACL_INSERT;
// Build Id expression, always use the default logic
- id = (Expr *)build_column_default(label_relation,
+ rel->id_expr = (Expr *)build_column_default(label_relation,
Anum_ag_label_edge_table_id);
- te = makeTargetEntry(id, InvalidAttrNumber, AGE_VARNAME_ID, false);
- targetList = lappend(targetList, te);
-
- rel->targetList = targetList;
-
// Build properties expression, if no map is given, use the default logic
alias = get_next_default_alias(cpstate);
resno = pstate->p_next_resno++;
@@ -2696,7 +2721,7 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list,
props = cypher_create_properties(cpstate, rel, label_relation, edge->props,
ENT_EDGE);
- rel->prop_var_no = resno - 1;
+ rel->prop_attr_num = resno - 1;
te = makeTargetEntry(props, resno, alias, false);
*target_list = lappend(*target_list, te);
@@ -2704,8 +2729,6 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list,
// Keep the lock
heap_close(label_relation, NoLock);
- rel->expr_states = NIL;
-
return rel;
}
@@ -2734,6 +2757,8 @@ static cypher_target_node *
transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list,
cypher_node *node)
{
+ ParseState *pstate = (ParseState *)cpstate;
+
/*
* Check if the variable already exists, if so find the entity and
* setup the target node
@@ -2746,8 +2771,13 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list,
if (entity)
{
+ if (entity->type != ENT_VERTEX)
+ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("variable %s already exists", node->name),
+ parser_errposition(pstate, node->location)));
+
return transform_create_cypher_existing_node(cpstate, target_list,
- entity, node);
+ entity->declared_in_current_clause, node);
}
}
@@ -2755,25 +2785,6 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list,
return transform_create_cypher_new_node(cpstate, target_list, node);
}
-static Expr *cypher_create_id_access_function(cypher_parsestate *cpstate,
- RangeTblEntry *rte,
- enum transform_entity_type type,
- char *name)
-{
- ParseState *pstate = (ParseState *)cpstate;
- Node *col;
- Oid func_oid;
- FuncExpr *func_expr;
- col = scanRTEForColumn(pstate, rte, name, -1, 0, NULL);
-
- func_oid = get_ag_func_oid(AG_ACCESS_FUNCTION_ID, 1, AGTYPEOID);
-
- func_expr = makeFuncExpr(func_oid, AGTYPEOID, list_make1(col), InvalidOid,
- InvalidOid, COERCE_EXPLICIT_CALL);
-
- return add_volatile_wrapper((Expr *)func_expr);
-}
-
/*
* Returns the resno for the TargetEntry with the resname equal to the name
* passed. Returns -1 otherwise.
@@ -2797,31 +2808,20 @@ static int get_target_entry_resno(List *target_list, char *name)
/*
* Transform logic for a previously declared variable in a CREATE clause.
- * All we need from the variable node is its id. Add an access to the
- * id and mark where in the target list we can find the id value.
+ * All we need from the variable node is its id, and whether we can skip
+ * some tests in the execution phase..
*/
static cypher_target_node *transform_create_cypher_existing_node(
- cypher_parsestate *cpstate, List **target_list, transform_entity *entity,
+ cypher_parsestate *cpstate, List **target_list, bool declared_in_current_clause,
cypher_node *node)
{
- ParseState *pstate = (ParseState *)cpstate;
cypher_target_node *rel = make_ag_node(cypher_target_node);
- char *alias;
- int resno;
- RangeTblEntry *rte = find_rte(cpstate, PREV_CYPHER_CLAUSE_ALIAS);
- TargetEntry *te;
- Expr *result;
rel->type = LABEL_KIND_VERTEX;
rel->flags = CYPHER_TARGET_NODE_FLAG_NONE;
-
+ rel->resultRelInfo = NULL;
rel->variable_name = node->name;
- rel->tuple_position = 0;
- if (entity->type != ENT_VERTEX)
- ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
- errmsg("variable %s already exists", node->name),
- parser_errposition(pstate, node->location)));
if (node->props)
ereport(
@@ -2835,39 +2835,19 @@ static cypher_target_node *transform_create_cypher_existing_node(
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("previously declared variables cannot have a label")));
- if (entity->target_node)
+ /*
+ * When the variable is declared in the same clause this vertex is a part of
+ * we can skip some expensive checks in the execution phase.
+ */
+ if (declared_in_current_clause)
{
- cypher_target_node *prev_target_node = entity->target_node;
-
- rel->flags |= CYPHER_TARGET_NODE_CUR_VAR;
- rel->id_var_no = prev_target_node->id_var_no;
- rel->tuple_position = prev_target_node->tuple_position;
-
- return rel;
+ rel->flags |= EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE;
}
- else
- {
- rel->flags |= CYPHER_TARGET_NODE_PREV_VAR;
-
- alias = get_next_default_alias(cpstate);
- resno = pstate->p_next_resno++;
-
- rel->relid = -1;
-
- // id
- result = cypher_create_id_access_function(cpstate, rte, ENT_VERTEX,
- node->name);
-
- rel->id_var_no = resno - 1;
- te = makeTargetEntry(result, resno, alias, false);
- *target_list = lappend(*target_list, te);
- rel->tuple_position = get_target_entry_resno(*target_list, node->name);
+ // get the AttrNumber the variable is stored in, so we can extract the id later.
+ rel->tuple_position = get_target_entry_resno(*target_list, node->name);
- rel->expr_states = NIL;
-
- return rel;
- }
+ return rel;
}
/*
@@ -2880,7 +2860,6 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate,
{
ParseState *pstate = (ParseState *)cpstate;
cypher_target_node *rel = make_ag_node(cypher_target_node);
- Node *id;
Relation label_relation;
RangeVar *rv;
RangeTblEntry *rte;
@@ -2890,10 +2869,17 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate,
int resno;
rel->type = LABEL_KIND_VERTEX;
+ rel->tuple_position = InvalidAttrNumber;
+ rel->variable_name = NULL;
+ rel->resultRelInfo = NULL;
if (!node->label)
{
rel->label_name = "";
+ /*
+ * If no label is specified, assign the generic label name that
+ * all labels are descendents of.
+ */
node->label = AG_DEFAULT_LABEL_VERTEX;
}
else
@@ -2929,16 +2915,8 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate,
rte->requiredPerms = ACL_INSERT;
// id
- id = cypher_create_id_default(cpstate, label_relation, ENT_VERTEX);
-
- te = makeTargetEntry((Expr *)id, InvalidAttrNumber, AGE_VARNAME_ID, false);
- rel->targetList = list_make1(te);
- rel->id_var_no = -1;
-
- alias = get_next_default_alias(cpstate);
- te = placeholder_target_entry(cpstate, alias);
- *target_list = lappend(*target_list, te);
- rel->id_var_no = te->resno;
+ rel->id_expr = (Expr *)build_column_default(label_relation,
+ Anum_ag_label_vertex_table_id);
// properties
alias = get_next_default_alias(cpstate);
@@ -2947,14 +2925,12 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate,
props = cypher_create_properties(cpstate, rel, label_relation, node->props,
ENT_VERTEX);
- rel->prop_var_no = resno - 1;
+ rel->prop_attr_num = resno - 1;
te = makeTargetEntry(props, resno, alias, false);
*target_list = lappend(*target_list, te);
heap_close(label_relation, NoLock);
- rel->expr_states = NIL;
-
if (node->name)
{
rel->variable_name = node->name;
@@ -2963,11 +2939,6 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate,
*target_list = lappend(*target_list, te);
rel->flags |= CYPHER_TARGET_NODE_IS_VAR;
}
- else
- {
- rel->variable_name = NULL;
- rel->tuple_position = 0;
- }
return rel;
}
@@ -3016,38 +2987,29 @@ static Expr *cypher_create_properties(cypher_parsestate *cpstate,
}
if (props)
+ {
properties = (Expr *)transform_cypher_expr(cpstate, props,
EXPR_KIND_INSERT_TARGET);
+ }
else if (type == ENT_VERTEX)
+ {
properties = (Expr *)build_column_default(
label_relation, Anum_ag_label_vertex_table_properties);
+ }
else if (type == ENT_EDGE)
+ {
properties = (Expr *)build_column_default(
label_relation, Anum_ag_label_edge_table_properties);
+ }
else
+ {
ereport(ERROR, (errmsg_internal("unreconized entity type")));
+ }
// add a volatile wrapper call to prevent the optimizer from removing it
return (Expr *)add_volatile_wrapper(properties);
}
-static Node *cypher_create_id_default(cypher_parsestate *cpstate,
- Relation label_relation,
- enum transform_entity_type type)
-{
- Node *id = NULL;
-
- if (type == ENT_VERTEX)
- id = build_column_default(label_relation,
- Anum_ag_label_vertex_table_id);
- else if (type == ENT_EDGE)
- id = build_column_default(label_relation, Anum_ag_label_edge_table_id);
- else
- ereport(ERROR, (errmsg_internal("unreconized entity type")));
-
- return id;
-}
-
/*
* makeNamespaceItem (from PG makeNamespaceItem)-
* Convenience subroutine to construct a ParseNamespaceItem.
@@ -3142,6 +3104,23 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate,
return rte;
}
+/*
+ * When we are done transforming a clause, before transforming the next clause
+ * iterate through the transform entities and mark them as not belonging to
+ * the clause that is currently being transformed.
+ */
+static void advance_transform_entities_to_next_clause(List *entities)
+{
+ ListCell *lc;
+
+ foreach (lc, entities)
+ {
+ transform_entity *entity = lfirst(lc);
+
+ entity->declared_in_current_clause = false;
+ }
+}
+
static Query *analyze_cypher_clause(transform_method transform,
cypher_clause *clause,
cypher_parsestate *parent_cpstate)
@@ -3159,6 +3138,8 @@ static Query *analyze_cypher_clause(transform_method transform,
query = transform(cpstate, clause);
+ advance_transform_entities_to_next_clause(cpstate->entities);
+
parent_cpstate->entities = list_concat(parent_cpstate->entities,
cpstate->entities);
free_cypher_parsestate(cpstate);
diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h
index 6deaa77..5cbd036 100644
--- a/src/include/nodes/cypher_nodes.h
+++ b/src/include/nodes/cypher_nodes.h
@@ -214,7 +214,7 @@ typedef struct cypher_create_path
{
ExtensibleNode extensible;
List *target_nodes;
- AttrNumber tuple_position;
+ AttrNumber path_attr_num;
} cypher_create_path;
#define CYPHER_CLAUSE_FLAG_NONE 0x0000
@@ -227,36 +227,72 @@ typedef struct cypher_create_path
#define CYPHER_CLAUSE_HAS_PREVIOUS_CLAUSE(flags) \
(flags & CYPHER_CLAUSE_FLAG_PREVIOUS_CLAUSE)
+/*
+ * Structure that contains all information to create
+ * a new entity in the create clause, or where to access
+ * this information if it doesn't need to be created.
+ *
+ * NOTE: This structure may be used for the MERGE clause as
+ * well
+ */
typedef struct cypher_target_node
{
ExtensibleNode extensible;
+ // 'v' for vertex or 'e' for edge
char type;
+ // flags defined below, prefaced with CYPHER_TARGET_NODE_FLAG_*
uint32 flags;
+ // if an edge, denotes direction
cypher_rel_dir dir;
- int id_var_no;
- int prop_var_no;
- List *targetList;
- TargetEntry *te;
- List *expr_states;
+ /*
+ * Used to create the id for the vertex/edge,
+ * if the CYPHER_TARGET_NODE_FLAG_INSERT flag
+ * is set. Doing it this way will protect us when
+ * rescan gets implemented. By calling the function
+ * that creates the id ourselves, we won't have an
+ * issue where the id could be created then not used.
+ * Since there is a limited number of ids available, we
+ * don't want to waste them.
+ */
+ Expr *id_expr;
+ ExprState *id_expr_state;
+ /*
+ * Attribute Number that this entity's properties
+ * are stored in the CustomScanState's child TupleTableSlot
+ */
+ AttrNumber prop_attr_num;
+ // RelInfo for the table this entity will be stored in
ResultRelInfo *resultRelInfo;
+ // elemTupleSlot used to insert the entity into its table
TupleTableSlot *elemTupleSlot;
+ // relid that the label stores its entity
Oid relid;
+ // label this entity belongs to.
char *label_name;
+ // variable name for this entity
char *variable_name;
+ /*
+ * Attribute number this entity needs to be stored in
+ * for parent execution nodes to reference it. If the
+ * entity is a varaible (CYPHER_TARGET_NODE_IS_VAR).
+ */
AttrNumber tuple_position;
} cypher_target_node;
#define CYPHER_TARGET_NODE_FLAG_NONE 0x0000
// node must insert data
#define CYPHER_TARGET_NODE_FLAG_INSERT 0x0001
-//node is a var declared in a previous clause
-#define CYPHER_TARGET_NODE_PREV_VAR 0x0002
-//node is a var declared in the current clause
-#define CYPHER_TARGET_NODE_CUR_VAR 0x0004
+/*
+ * Flag that denotes if this target node is referencing
+ * a variable that was already created AND created in the
+ * same clause.
+ */
+#define EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE 0x0002
+
//node is the first instance of a declared variable
-#define CYPHER_TARGET_NODE_IS_VAR 0x0008
+#define CYPHER_TARGET_NODE_IS_VAR 0x0004
// node is an element in a path variable
-#define CYPHER_TARGET_NODE_IN_PATH_VAR 0x0010
+#define CYPHER_TARGET_NODE_IN_PATH_VAR 0x0008
#define CYPHER_TARGET_NODE_OUTPUT(flags) \
(flags & (CYPHER_TARGET_NODE_IS_VAR | CYPHER_TARGET_NODE_IN_PATH_VAR))
@@ -267,12 +303,12 @@ typedef struct cypher_target_node
#define CYPHER_TARGET_NODE_IS_VARIABLE(flags) \
(flags & CYPHER_TARGET_NODE_IS_VAR)
-#define CYPHER_TARGET_NODE_ID_IS_GRAPHID(flags) \
- (flags & CYPHER_TARGET_NODE_CUR_VAR)
-
-#define CYPHER_TARGET_NODE_ID_IS_AGTYPE(flags) \
- (flags & CYPHER_TARGET_NODE_PREV_VAR)
-
+/*
+ * When a vertex is created and is reference in the same clause
+ * later. We don't need to check to see if the vertex still exists.
+ */
+#define SAFE_TO_SKIP_EXISTENCE_CHECK(flags) \
+ (flags & EXISTING_VARAIBLE_DECLARED_SAME_CLAUSE)
#define CYPHER_TARGET_NODE_INSERT_ENTITY(flags) \
(flags & CYPHER_TARGET_NODE_FLAG_INSERT)