From 606f820b539cbd6aadb2caaeaa7db5a41405ce84 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:36 +0900 Subject: [PATCH 01/15] Document Cypher execution planning goals Document the PostgreSQL-backed execution direction for Cypher planning and record the local WSL regression workflow used while iterating on the branch. Remove the obsolete session-info side channel from Cypher analysis and keep the planning notes aligned with that simpler data flow. This gives the later performance work a clearer baseline and records how to run focused tests on this checkout without hitting Windows mount permission issues. --- regress/expected/analyze.out | 28 ++--- regress/sql/analyze.sql | 6 +- src/backend/parser/cypher_analyze.c | 105 +++------------- src/backend/utils/adt/age_session_info.c | 150 ++--------------------- src/include/utils/age_session_info.h | 12 +- 5 files changed, 42 insertions(+), 259 deletions(-) diff --git a/regress/expected/analyze.out b/regress/expected/analyze.out index 676dab9da..84b3c71ad 100644 --- a/regress/expected/analyze.out +++ b/regress/expected/analyze.out @@ -60,7 +60,7 @@ SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); ERROR: syntax error at end of input LINE 1: SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); ^ --- should return false due to invalid input to age_prepare_function +-- age_prepare_cypher is a compatibility stub and must not affect cypher() SELECT * FROM age_prepare_cypher(NULL, NULL); age_prepare_cypher -------------------- @@ -79,46 +79,40 @@ SELECT * FROM age_prepare_cypher(NULL, ''); f (1 row) --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', ''); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); -ERROR: syntax error at end of input +ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); - ^ --- should return true and execute cypher command + ^ SELECT * FROM age_prepare_cypher('analyze', 'MATCH (u) RETURN (u)'); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); - result ----------------------------------------------------------------- - {"id": 281474976710657, "label": "", "properties": {}}::vertex -(1 row) - --- should error due to invalid input to cypher function +ERROR: a name constant is expected +LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); + ^ SELECT * FROM cypher(NULL, NULL) AS (result agtype); ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); ^ --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', '$$ $$'); age_prepare_cypher -------------------- - t + f (1 row) SELECT * FROM cypher(NULL, NULL) AS (result agtype); -ERROR: unexpected character at or near "$" +ERROR: a name constant is expected LINE 1: SELECT * FROM cypher(NULL, NULL) AS (result agtype); - ^ + ^ -- should return errors SELECT * FROM cypher() AS (result agtype); ERROR: cypher function requires a minimum of 2 arguments diff --git a/regress/sql/analyze.sql b/regress/sql/analyze.sql index 1da70b239..f0bc6d9dc 100644 --- a/regress/sql/analyze.sql +++ b/regress/sql/analyze.sql @@ -36,19 +36,15 @@ SELECT * FROM cypher('analyze', '') AS (result agtype); -- should error due to bad cypher statement SELECT * FROM cypher('analyze', $$ $$) AS (result agtype); --- should return false due to invalid input to age_prepare_function +-- age_prepare_cypher is a compatibility stub and must not affect cypher() SELECT * FROM age_prepare_cypher(NULL, NULL); SELECT * FROM age_prepare_cypher('analyze', NULL); SELECT * FROM age_prepare_cypher(NULL, ''); --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', ''); SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should return true and execute cypher command SELECT * FROM age_prepare_cypher('analyze', 'MATCH (u) RETURN (u)'); SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should error due to invalid input to cypher function SELECT * FROM cypher(NULL, NULL) AS (result agtype); --- should return true but cypher should fail SELECT * FROM age_prepare_cypher('analyze', '$$ $$'); SELECT * FROM cypher(NULL, NULL) AS (result agtype); diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index b2c9256ce..fc5766858 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -33,7 +33,6 @@ #include "parser/cypher_clause.h" #include "parser/cypher_parser.h" #include "utils/ag_func.h" -#include "utils/age_session_info.h" typedef bool (*cypher_expression_condition)(Node *expr); @@ -437,103 +436,31 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) */ query_str = expr_get_const_cstring(arg2, pstate->p_sourcetext); - /* - * Validate appropriate cypher function usage - - * - * Session info OVERRIDES ANY INPUT PASSED and if any is passed, it will - * cause the cypher function to error out. - * - * If this is using session info, both of the first 2 input parameters need - * to be NULL, in addition to the session info being set up. Furthermore, - * the input parameters passed in by session info need to both be non-NULL. - * - * If this is not using session info, both input parameters need to be - * non-NULL. - * - */ - if (is_session_info_prepared()) + /* get the graph name string from the passed parameters */ + if (!graph_name) { - /* check to see if either input parameter is non-NULL*/ - if (graph_name != NULL || query_str != NULL) - { - Node *arg = (graph_name == NULL) ? arg1 : arg2; - - /* - * Make sure to clean up session info because the ereport will - * cause the function to exit. - */ - reset_session_info(); - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("session info requires cypher(NULL, NULL) to be passed"), - parser_errposition(pstate, exprLocation(arg)))); - } - /* get our input parameters from session info */ - else - { - graph_name_str = get_session_info_graph_name(); - query_str = get_session_info_cypher_statement(); - - /* check to see if either are NULL */ - if (graph_name_str == NULL || query_str == NULL) - { - /* - * Make sure to clean up session info because the ereport will - * cause the function to exit. - */ - reset_session_info(); - - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("both session info parameters need to be non-NULL"), - parser_errposition(pstate, -1))); - } - } + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a name constant is expected"), + parser_errposition(pstate, exprLocation(arg1)))); } - /* otherwise, we get the parameters from the passed function input */ else { - /* get the graph name string from the passed parameters */ - if (!graph_name) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a name constant is expected"), - parser_errposition(pstate, exprLocation(arg1)))); - } - else - { - graph_name_str = NameStr(*graph_name); - } - /* get the query string from the passed parameters */ - if (!query_str) - { - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("a dollar-quoted string constant is expected"), - parser_errposition(pstate, exprLocation(arg2)))); - } + graph_name_str = NameStr(*graph_name); } - /* - * The session info is only valid for one cypher call. Now that we are done - * with it, if it was used, we need to reset it to free the memory used. - * Additionally, the query location is dependent on how we got the query - * string, so set the location accordingly. - */ - if (is_session_info_prepared()) - { - reset_session_info(); - query_loc = 0; - } - else + /* get the query string from the passed parameters */ + if (!query_str) { - /* this call will crash if we use session info */ - query_loc = get_query_location(((Const *)arg2)->location, - pstate->p_sourcetext); + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("a dollar-quoted string constant is expected"), + parser_errposition(pstate, exprLocation(arg2)))); } + query_loc = get_query_location(((Const *)arg2)->location, + pstate->p_sourcetext); + /* validate the graph exists */ graph_oid = get_graph_oid(graph_name_str); if (!OidIsValid(graph_oid)) diff --git a/src/backend/utils/adt/age_session_info.c b/src/backend/utils/adt/age_session_info.c index f224d4064..71322389d 100644 --- a/src/backend/utils/adt/age_session_info.c +++ b/src/backend/utils/adt/age_session_info.c @@ -19,152 +19,20 @@ #include "postgres.h" -#include "funcapi.h" - -#include -#include "utils/age_session_info.h" - -/* - * static/global session info variables for use with the driver interface. - */ -static int session_info_pid = -1; -static char *session_info_graph_name = NULL; -static char *session_info_cypher_statement = NULL; -static bool session_info_prepared = false; - -static void set_session_info(char *graph_name, char *cypher_statement); - -/* function to set the session info. it will clean it, if necessary. */ -static void set_session_info(char *graph_name, char *cypher_statement) -{ - MemoryContext oldctx = NULL; - - if (is_session_info_prepared()) - { - reset_session_info(); - } - - /* we need to use a higher memory context for the data pointed to. */ - oldctx = MemoryContextSwitchTo(TopMemoryContext); - - if (graph_name != NULL) - { - session_info_graph_name = pstrdup(graph_name); - } - else - { - session_info_graph_name = NULL; - } - - if (cypher_statement != NULL) - { - session_info_cypher_statement = pstrdup(cypher_statement); - } - else - { - session_info_cypher_statement = NULL; - } - - /* switch back to the original context */ - MemoryContextSwitchTo(oldctx); - - session_info_pid = getpid(); - session_info_prepared = true; -} - -/* - * Helper function to return the value of session_info_cypher_statement or NULL - * if the value isn't set. The value returned is a copy, so please free it when - * done. - */ -char *get_session_info_graph_name(void) -{ - if (is_session_info_prepared() && - session_info_graph_name != NULL) - { - return pstrdup(session_info_graph_name); - } - - return NULL; -} +#include "fmgr.h" /* - * Helper function to return the value of session_info_cypher_statement or NULL - * if the value isn't set. The value returned is a copy, so please free it when - * done. + * Compatibility stub for the old driver-facing two-step execution path. + * + * The previous implementation stored graph/query text in backend-global state + * and then let a later cypher(NULL, NULL) call consume that state. The parser + * now requires cypher() to receive its graph and query text directly, keeping + * cypher query execution on PostgreSQL's normal parse/plan lifecycle instead + * of an AGE-managed side channel. */ -char *get_session_info_cypher_statement(void) -{ - if (is_session_info_prepared() && - session_info_cypher_statement != NULL) - { - return pstrdup(session_info_cypher_statement); - } - - return NULL; -} - -/* function to return the state of the session info data */ -bool is_session_info_prepared(void) -{ - /* is the session info prepared AND is the pid the same pid */ - if (session_info_prepared == true && - session_info_pid == getpid()) - { - return true; - } - - return false; -} - -/* function to clean and reset the session info back to default values */ -void reset_session_info(void) -{ - /* if the session info is prepared, free the strings */ - if (session_info_prepared == true) - { - if (session_info_graph_name != NULL) - { - pfree_if_not_null(session_info_graph_name); - } - - if (session_info_cypher_statement != NULL) - { - pfree_if_not_null(session_info_cypher_statement); - } - } - - /* reset the session info back to default unused values */ - session_info_graph_name = NULL; - session_info_cypher_statement = NULL; - session_info_prepared = false; - session_info_pid = -1; -} - -/* AGE SQL function to prepare session info */ PG_FUNCTION_INFO_V1(age_prepare_cypher); Datum age_prepare_cypher(PG_FUNCTION_ARGS) { - char *graph_name_str = NULL; - char *cypher_statement_str = NULL; - - /* both arguments must be non-NULL */ - if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) - { - PG_RETURN_BOOL(false); - } - - graph_name_str = PG_GETARG_CSTRING(0); - cypher_statement_str = PG_GETARG_CSTRING(1); - - /* both strings must be non-NULL */ - if (graph_name_str == NULL || cypher_statement_str == NULL) - { - PG_RETURN_BOOL(false); - } - - set_session_info(graph_name_str, cypher_statement_str); - - PG_RETURN_BOOL(true); + PG_RETURN_BOOL(false); } diff --git a/src/include/utils/age_session_info.h b/src/include/utils/age_session_info.h index 5bd072fb6..e97dff59f 100644 --- a/src/include/utils/age_session_info.h +++ b/src/include/utils/age_session_info.h @@ -20,12 +20,10 @@ #ifndef AGE_SESSION_INFO_H #define AGE_SESSION_INFO_H -#include "utils/agtype.h" - -bool is_session_info_prepared(void); -char *get_session_info_graph_name(void); -char *get_session_info_cypher_statement(void); -void reset_session_info(void); +/* + * The old session-info side channel was removed from cypher() analysis. + * This header remains only as a compatibility include for code that has not + * been cleaned up yet. + */ #endif - From db90eee940c749bd074e4bee57177fb9719ee138 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:36 +0900 Subject: [PATCH 02/15] Optimize Cypher DML executor lookups Reduce repeated executor setup in SET, DELETE, MERGE, and entity-existence paths by carrying reusable lookup state across rows. The combined change keeps label relations and endpoint indexes open where the executor can safely reuse them, defers result-relation and RLS setup until the write path actually needs it, and avoids scans when a DELETE cannot touch connected edges. It also switches common id lookups to the primary-key fast path and uses read locks for read-only existence and endpoint checks. --- src/backend/catalog/ag_label.c | 140 ++++------- src/backend/executor/cypher_create.c | 16 +- src/backend/executor/cypher_delete.c | 248 +++++++++++------- src/backend/executor/cypher_merge.c | 46 ++-- src/backend/executor/cypher_set.c | 304 ++++++++++++++--------- src/backend/executor/cypher_utils.c | 105 ++++---- src/backend/optimizer/cypher_pathnode.c | 56 ++++- src/backend/parser/cypher_clause.c | 31 ++- src/backend/utils/adt/age_global_graph.c | 167 ++++--------- src/backend/utils/adt/agtype.c | 139 ++++++----- src/include/catalog/ag_label.h | 8 +- src/include/executor/cypher_utils.h | 17 +- 12 files changed, 686 insertions(+), 591 deletions(-) diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index d407626fd..4f99db51f 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -20,6 +20,8 @@ #include "postgres.h" #include "access/genam.h" +#include "access/heapam.h" +#include "access/tableam.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "executor/executor.h" @@ -30,7 +32,6 @@ #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "commands/label_commands.h" -#include "executor/cypher_utils.h" #include "utils/ag_cache.h" /* @@ -283,130 +284,73 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, } /* - * Retrieves a list of all the names of a graph. + * Retrieves a list of all the edge labels of a graph. * * XXX: We may want to use the cache system for this function, * however the cache system currently requires us to know the * name of the label we want. */ -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid) +List *get_all_edge_labels_per_graph(Snapshot snapshot, Oid graph_oid) { List *labels = NIL; - ScanKeyData scan_keys[2]; + ScanKeyData scan_key; Relation ag_label; - TableScanDesc scan_desc; + SysScanDesc scan_desc; HeapTuple tuple; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; - Oid index_oid; + TupleDesc tupdesc; /* setup the table to be scanned */ ag_label = table_open(ag_label_relation_id(), AccessShareLock); + tupdesc = RelationGetDescr(ag_label); - index_oid = find_usable_btree_index_for_attr(ag_label, Anum_ag_label_graph); + ScanKeyInit(&scan_key, Anum_ag_label_graph, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graph_oid)); - resultRelInfo = create_entity_result_rel_info(estate, "ag_catalog", - "ag_label"); + scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), + true, snapshot, 1, &scan_key); - if (OidIsValid(index_oid)) + while (HeapTupleIsValid(tuple = systable_getnext(scan_desc))) { - Relation index_rel; - IndexScanDesc index_scan_desc; - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsBufferHeapTuple); - - index_rel = index_open(index_oid, AccessShareLock); - - /* - * Use 1 as the attribute number because 'graph' is the 1st column - * in the ag_label_graph_oid_index - */ - ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - - index_scan_desc = index_beginscan(ag_label, index_rel, estate->es_snapshot, NULL, 1, 0); - index_rescan(index_scan_desc, scan_keys, 1, NULL, 0); - - while (index_getnext_slot(index_scan_desc, ForwardScanDirection, slot)) + ag_label_info *label_info; + Name label; + bool is_null; + Datum datum; + Oid label_relation; + + datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); + if (is_null || DatumGetChar(datum) != LABEL_TYPE_EDGE) { - Name label; - Name lval; - bool isNull; - Datum datum; - char kind; - - /*There isn't field kind in index. So we should check it by hands*/ - datum = slot_getattr(slot, Anum_ag_label_kind, &isNull); - if (isNull) - { - continue; - } - - kind = DatumGetChar(datum); - - if (kind != LABEL_TYPE_EDGE) - { - continue; - } - - datum = slot_getattr(slot, Anum_ag_label_name, &isNull); - if (!isNull) - { - label = DatumGetName(datum); - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); - } + continue; } - index_endscan(index_scan_desc); - index_close(index_rel, AccessShareLock); - } - else - { - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - /* setup scan keys to get all edges for the given graph oid */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, - F_CHAREQ, CharGetDatum(LABEL_TYPE_EDGE)); - - scan_desc = table_beginscan(ag_label, estate->es_snapshot, 2, scan_keys); - - /* scan through the results and get all the label names. */ - while(true) + datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); + if (is_null) { - Name label; - Name lval; - bool isNull; - Datum datum; - - tuple = heap_getnext(scan_desc, ForwardScanDirection); - - /* no more labels to process */ - if (!HeapTupleIsValid(tuple)) - break; + continue; + } - ExecStoreHeapTuple(tuple, slot, false); + label = DatumGetName(datum); - datum = slot_getattr(slot, Anum_ag_label_name, &isNull); - label = DatumGetName(datum); + datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, &is_null); + if (is_null) + { + continue; + } - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); + label_relation = DatumGetObjectId(datum); + if (!OidIsValid(label_relation)) + { + continue; } - table_endscan(scan_desc); + label_info = palloc(sizeof(ag_label_info)); + label_info->name = pstrdup(NameStr(*label)); + label_info->relation = label_relation; + labels = lappend(labels, label_info); } - destroy_entity_result_rel_info(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, AccessShareLock); + systable_endscan(scan_desc); + table_close(ag_label, AccessShareLock); return labels; } diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 876b6f250..dbda0da2e 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -145,6 +145,9 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + css->entity_exists_index_cache = + create_entity_exists_index_cache("create_entity_exists_index_cache"); + Increment_Estate_CommandId(estate); } @@ -231,7 +234,7 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) * inserted and the current command Id was not used. So, only flag it * if there is a non empty pattern. */ - if (list_length(css->pattern) > 0) + if (css->pattern != NIL) { /* the current command Id has been used */ used = true; @@ -274,6 +277,12 @@ static void end_cypher_create(CustomScanState *node) ExecEndNode(node->ss.ps.lefttree); + if (css->entity_exists_index_cache != NULL) + { + hash_destroy(css->entity_exists_index_cache); + css->entity_exists_index_cache = NULL; + } + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); @@ -590,7 +599,9 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, */ if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) + if (!entity_exists_with_cache(estate, css->graph_oid, + DATUM_GET_GRAPHID(id), + css->entity_exists_index_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -615,4 +626,3 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, return id; } - diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 19eca3ad6..a354b6b18 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -29,6 +29,7 @@ #include "catalog/ag_label.h" #include "executor/cypher_executor.h" #include "utils/age_global_graph.h" +#include "utils/ag_cache.h" #include "executor/cypher_utils.h" static void begin_cypher_delete(CustomScanState *node, EState *estate, @@ -40,11 +41,17 @@ static void rescan_cypher_delete(CustomScanState *node); static void process_delete_list(CustomScanState *node); static void check_for_connected_edges(CustomScanState *node); +static void ensure_detach_delete_rls(CustomScanState *node, + ResultRelInfo *resultRelInfo, + Oid relid, bool *rls_checked, + bool *rls_enabled, List **qualExprs, + ExprContext **econtext); static agtype_value *extract_entity(CustomScanState *node, TupleTableSlot *scanTupleSlot, int entity_position); static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple); +static void init_delete_caches(cypher_delete_custom_scan_state *css); const CustomExecMethods cypher_delete_exec_methods = {DELETE_SCAN_STATE_NAME, begin_cypher_delete, @@ -95,13 +102,6 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } - /* - * Get all the labels that are visible to this delete clause at this point - * in the transaction. To be used later when the delete clause finds - * vertices. - */ - css->edge_labels = get_all_edge_labels_per_graph(estate, css->delete_data->graph_oid); - /* init vertex_id_htab */ MemSet(&hashctl, 0, sizeof(hashctl)); hashctl.keysize = sizeof(graphid); @@ -111,6 +111,7 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, css->vertex_id_htab = hash_create(DELETE_VERTEX_HTAB_NAME, DELETE_VERTEX_HTAB_SIZE, &hashctl, HASH_ELEM | HASH_FUNCTION); + init_delete_caches(css); /* * Postgres does not assign the es_output_cid in queries that do @@ -202,11 +203,43 @@ static void end_cypher_delete(CustomScanState *node) /* invalidate VLE cache — graph was mutated */ increment_graph_version(css->delete_data->graph_oid); + if (css->qual_cache != NULL) + { + hash_destroy(css->qual_cache); + css->qual_cache = NULL; + } + + if (css->index_cache != NULL) + { + hash_destroy(css->index_cache); + css->index_cache = NULL; + } + hash_destroy(((cypher_delete_custom_scan_state *)node)->vertex_id_htab); ExecEndNode(node->ss.ps.lefttree); } +static void init_delete_caches(cypher_delete_custom_scan_state *css) +{ + HASHCTL hashctl; + HASHCTL idx_hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(RLSCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + css->qual_cache = hash_create("delete_qual_cache", 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); + idx_hashctl.keysize = sizeof(Oid); + idx_hashctl.entrysize = sizeof(IndexCacheEntry); + idx_hashctl.hcxt = CurrentMemoryContext; + css->index_cache = hash_create("delete_index_cache", 8, &idx_hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + /* * Used for rewinding the scan state and reprocessing the results. * @@ -381,25 +414,8 @@ static void process_delete_list(CustomScanState *node) ExprContext *econtext = css->css.ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; EState *estate = node->ss.ps.state; - HTAB *qual_cache = NULL; - HASHCTL hashctl; - HTAB *index_cache = NULL; - HASHCTL idx_hashctl; - - /* Hash table for caching compiled security quals per label */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(Oid); - hashctl.entrysize = sizeof(RLSCacheEntry); - hashctl.hcxt = CurrentMemoryContext; - qual_cache = hash_create("delete_qual_cache", 8, &hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - - MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); - idx_hashctl.keysize = sizeof(Oid); - idx_hashctl.entrysize = sizeof(IndexCacheEntry); - idx_hashctl.hcxt = CurrentMemoryContext; - index_cache = hash_create("delete_index_cache", 8, &idx_hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + HTAB *qual_cache = css->qual_cache; + HTAB *index_cache = css->index_cache; foreach(lc, css->delete_data->delete_items) { @@ -410,6 +426,7 @@ static void process_delete_list(CustomScanState *node) ResultRelInfo *resultRelInfo; HeapTuple heap_tuple = NULL; char *label_name; + label_cache_data *label_cache; Integer *pos; int entity_position; Oid relid; @@ -421,7 +438,9 @@ static void process_delete_list(CustomScanState *node) IndexScanDesc index_scan_desc = NULL; bool shouldFree = false; IndexCacheEntry *idx_entry; - bool found_idx_entry; + bool found_idx_entry; + RLSCacheEntry *rls_entry; + bool found_rls_entry; item = lfirst(lc); @@ -438,8 +457,17 @@ static void process_delete_list(CustomScanState *node) id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); label = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "label"); label_name = pnstrdup(label->val.string.val, label->val.string.len); + label_cache = search_label_graph_oid_cache(css->delete_data->graph_oid, + GET_LABEL_ID(id->val.int_value)); + if (label_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" does not exist", label_name))); + } - resultRelInfo = create_entity_result_rel_info(estate, css->delete_data->graph_name, label_name); + resultRelInfo = create_entity_result_rel_info_by_oid( + estate, label_cache->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); @@ -476,6 +504,23 @@ static void process_delete_list(CustomScanState *node) index_oid = idx_entry->index_oid; + rls_entry = hash_search(qual_cache, &relid, HASH_ENTER, + &found_rls_entry); + if (!found_rls_entry) + { + rls_entry->rls_enabled = + check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED; + + if (rls_entry->rls_enabled) + { + rls_entry->qualExprs = setup_security_quals(resultRelInfo, + estate, node, + CMD_DELETE); + rls_entry->slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(rel), &TTSOpsHeapTuple); + } + } + /* * Setup the scan description, with the correct snapshot and scan keys. */ @@ -507,21 +552,12 @@ static void process_delete_list(CustomScanState *node) bool passed_rls = true; /* Check RLS security quals (USING policy) before delete */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - bool found_rls; - - entry = hash_search(qual_cache, &relid, HASH_ENTER, &found_rls); - if (!found_rls) - { - entry->qualExprs = setup_security_quals(resultRelInfo, estate, node, CMD_DELETE); - entry->slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), &TTSOpsHeapTuple); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, false); - if (!check_security_quals(entry->qualExprs, entry->slot, econtext)) + if (!check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext)) { passed_rls = false; } @@ -565,9 +601,6 @@ static void process_delete_list(CustomScanState *node) destroy_entity_result_rel_info(resultRelInfo); } - /* Clean up the cache */ - hash_destroy(qual_cache); - hash_destroy(index_cache); } /* @@ -581,10 +614,12 @@ static void process_edges_by_index(Oid index_oid, ResultRelInfo *resultRelInfo, Oid relid, char *label_name, - bool rls_enabled, - List *qualExprs, - ExprContext *econtext, - bool is_pass_two) + bool *rls_checked, + bool *rls_enabled, + List **qualExprs, + ExprContext **econtext, + bool is_pass_two, + bool *delete_acl_checked) { HASH_SEQ_STATUS hash_status; graphid *vid; @@ -630,21 +665,31 @@ static void process_edges_by_index(Oid index_oid, /* If edge found - delete it (or error if not DETACH) */ if (css->delete_data->detach) { - AclResult aclresult; bool shouldFree; HeapTuple tuple; - /* Check that the user has DELETE permission on the edge table */ - aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_DELETE); - if (aclresult != ACLCHECK_OK) + if (!*delete_acl_checked) { - aclcheck_error(aclresult, OBJECT_TABLE, label_name); + AclResult aclresult; + + aclresult = pg_class_aclcheck(relid, GetUserId(), + ACL_DELETE); + if (aclresult != ACLCHECK_OK) + { + aclcheck_error(aclresult, OBJECT_TABLE, label_name); + } + + *delete_acl_checked = true; } + ensure_detach_delete_rls(&css->css, resultRelInfo, relid, + rls_checked, rls_enabled, qualExprs, + econtext); + /* Check RLS security quals (USING policy) before delete */ - if (rls_enabled) + if (*rls_enabled) { - if (!check_security_quals(qualExprs, slot, econtext)) + if (!check_security_quals(*qualExprs, slot, *econtext)) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -677,6 +722,28 @@ static void process_edges_by_index(Oid index_oid, ExecDropSingleTupleTableSlot(slot); } +static void ensure_detach_delete_rls(CustomScanState *node, + ResultRelInfo *resultRelInfo, + Oid relid, bool *rls_checked, + bool *rls_enabled, List **qualExprs, + ExprContext **econtext) +{ + if (*rls_checked) + { + return; + } + + *rls_checked = true; + + if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + { + *rls_enabled = true; + *econtext = node->ss.ps.ps_ExprContext; + *qualExprs = setup_security_quals(resultRelInfo, node->ss.ps.state, + node, CMD_DELETE); + } +} + /* * Scans the edge tables and checks if the deleted vertices are connected to * any edge(s). For DETACH DELETE, the connected edges are deleted. Otherwise, @@ -688,47 +755,48 @@ static void check_for_connected_edges(CustomScanState *node) cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; EState *estate = css->css.ss.ps.state; - char *graph_name = css->delete_data->graph_name; + + if (hash_get_num_entries(css->vertex_id_htab) == 0) + { + return; + } + + /* + * Edge labels are only needed after at least one vertex was deleted. + * Edge-only DELETEs and empty inputs can skip the catalog lookup entirely. + */ + if (css->edge_labels == NIL) + { + css->edge_labels = get_all_edge_labels_per_graph(estate->es_snapshot, + css->delete_data->graph_oid); + } /* scans each label from css->edge_labels */ foreach (lc, css->edge_labels) { - char *label_name = lfirst(lc); + ag_label_info *label_info = lfirst(lc); + char *label_name = label_info->name; ResultRelInfo *resultRelInfo; TableScanDesc scan_desc; HeapTuple tuple; TupleTableSlot *slot; Oid relid; + bool rls_checked = false; bool rls_enabled = false; + bool delete_acl_checked = false; List *qualExprs = NIL; ExprContext *econtext = NULL; Oid start_index_oid = InvalidOid; Oid end_index_oid = InvalidOid; Relation rel; - resultRelInfo = create_entity_result_rel_info(estate, graph_name, - label_name); + resultRelInfo = create_entity_result_rel_info_by_oid( + estate, label_info->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); estate->es_snapshot->curcid = GetCurrentCommandId(false); estate->es_output_cid = GetCurrentCommandId(false); - /* - * For DETACH DELETE with RLS enabled, compile the security qual - * expressions once per label for efficient evaluation. - */ - if (css->delete_data->detach) - { - /* Setup RLS security quals for this label */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) - { - rls_enabled = true; - econtext = css->css.ss.ps.ps_ExprContext; - qualExprs = setup_security_quals(resultRelInfo, estate, node, - CMD_DELETE); - } - } - /* Look for indexes on start_id and end_id columns. */ start_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_start_id); end_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_end_id); @@ -737,11 +805,15 @@ static void check_for_connected_edges(CustomScanState *node) { /* PASS 1: Find edges where the deleted vertex is the START_ID. */ process_edges_by_index(start_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, rls_enabled, qualExprs, econtext, false); + relid, label_name, &rls_checked, + &rls_enabled, &qualExprs, &econtext, false, + &delete_acl_checked); /* PASS 2: Find edges where the deleted vertex is the END_ID. */ process_edges_by_index(end_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, rls_enabled, qualExprs, econtext, true); + relid, label_name, &rls_checked, + &rls_enabled, &qualExprs, &econtext, true, + &delete_acl_checked); } else { @@ -787,15 +859,25 @@ static void check_for_connected_edges(CustomScanState *node) { if (css->delete_data->detach) { - AclResult aclresult; - - /* Check that the user has DELETE permission on the edge table */ - aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_DELETE); - if (aclresult != ACLCHECK_OK) + if (!delete_acl_checked) { - aclcheck_error(aclresult, OBJECT_TABLE, label_name); + AclResult aclresult; + + aclresult = pg_class_aclcheck(relid, GetUserId(), + ACL_DELETE); + if (aclresult != ACLCHECK_OK) + { + aclcheck_error(aclresult, OBJECT_TABLE, + label_name); + } + + delete_acl_checked = true; } + ensure_detach_delete_rls(node, resultRelInfo, relid, + &rls_checked, &rls_enabled, + &qualExprs, &econtext); + /* Check RLS security quals (USING policy) before delete */ if (rls_enabled) { diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 2b3d1f7dd..19b679a7f 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -237,6 +237,9 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, /* store the currentCommandId for this instance */ css->base_currentCommandId = GetCurrentCommandId(false); + css->entity_exists_index_cache = + create_entity_exists_index_cache("merge_entity_exists_index_cache"); + Increment_Estate_CommandId(estate); } @@ -442,13 +445,12 @@ static path_entry **prebuild_path(CustomScanState *node) cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; List *nodes = css->path->target_nodes; - int path_length = list_length(nodes); ListCell *lc = NULL; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; int counter = 0; path_entry **path_array = NULL; - path_array = palloc0(sizeof(path_entry *) * path_length); + path_array = palloc0(sizeof(path_entry *) * css->path_length); /* iterate through the path, partially prebuilding it */ foreach (lc, nodes) @@ -519,7 +521,9 @@ static path_entry **prebuild_path(CustomScanState *node) if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, entry->id)) + if (!entity_exists_with_cache(estate, css->graph_oid, + entry->id, + css->entity_exists_index_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -597,7 +601,6 @@ static path_entry **find_duplicate_path(CustomScanState *node, { cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; - int path_length = list_length(css->path->target_nodes); /* if the list is NULL just return NULL */ if (css->created_paths_list == NULL) @@ -614,7 +617,8 @@ static path_entry **find_duplicate_path(CustomScanState *node, while (curr_path != NULL) { /* if we have found the entry, return it */ - if (compare_2_paths(path_array, curr_path->entry, path_length)) + if (compare_2_paths(path_array, curr_path->entry, + css->path_length)) { return curr_path->entry; } @@ -683,7 +687,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) if (!terminal && !css->eager_buffer_filled) { css->eager_tuples = NIL; - css->eager_tuples_index = 0; + css->eager_tuples_cursor = NULL; while (true) { @@ -713,8 +717,6 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; - int path_length = - list_length(css->path->target_nodes); prebuilt_path_array = prebuild_path(node); @@ -724,7 +726,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) if (found_path_array) { free_path_entry_array(prebuilt_path_array, - path_length); + css->path_length); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ @@ -768,20 +770,22 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) } css->eager_buffer_filled = true; + css->eager_tuples_cursor = list_head(css->eager_tuples); } /* Non-terminal: return the next buffered row (or NULL if empty) */ if (!terminal && css->eager_buffer_filled) { - if (css->eager_tuples_index < list_length(css->eager_tuples)) + if (css->eager_tuples_cursor != NULL) { HeapTuple htup; TupleTableSlot *result_slot = node->ss.ps.ps_ResultTupleSlot; htup = (HeapTuple) - list_nth(css->eager_tuples, css->eager_tuples_index); - css->eager_tuples_index++; + lfirst(css->eager_tuples_cursor); + css->eager_tuples_cursor = + lnext(css->eager_tuples, css->eager_tuples_cursor); ExecForceStoreHeapTuple(htup, result_slot, false); return result_slot; @@ -817,7 +821,6 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; - int path_length = list_length(css->path->target_nodes); prebuilt_path_array = prebuild_path(node); @@ -826,7 +829,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) if (found_path_array) { - free_path_entry_array(prebuilt_path_array, path_length); + free_path_entry_array(prebuilt_path_array, + css->path_length); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ @@ -1045,7 +1049,6 @@ static void end_cypher_merge(CustomScanState *node) (cypher_merge_custom_scan_state *)node; cypher_create_path *path = css->path; ListCell *lc = NULL; - int path_length = list_length(path->target_nodes); /* increment the command counter */ CommandCounterIncrement(); @@ -1058,6 +1061,12 @@ static void end_cypher_merge(CustomScanState *node) ExecEndNode(node->ss.ps.lefttree); + if (css->entity_exists_index_cache != NULL) + { + hash_destroy(css->entity_exists_index_cache); + css->entity_exists_index_cache = NULL; + } + foreach (lc, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc); @@ -1082,7 +1091,7 @@ static void end_cypher_merge(CustomScanState *node) path_entry **entry = css->created_paths_list->entry; /* free up the path array elements */ - free_path_entry_array(entry, path_length); + free_path_entry_array(entry, css->path_length); /* free up the array container */ pfree_if_not_null(entry); @@ -1146,6 +1155,7 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) cypher_css->flags = merge_information->flags; cypher_css->merge_function_attr = merge_information->merge_function_attr; cypher_css->path = merge_information->path; + cypher_css->path_length = list_length(cypher_css->path->target_nodes); cypher_css->created_new_path = false; cypher_css->found_a_path = false; cypher_css->graph_oid = merge_information->graph_oid; @@ -1431,7 +1441,9 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, */ if (!SAFE_TO_SKIP_EXISTENCE_CHECK(node->flags)) { - if (!entity_exists(estate, css->graph_oid, DATUM_GET_GRAPHID(id))) + if (!entity_exists_with_cache(estate, css->graph_oid, + DATUM_GET_GRAPHID(id), + css->entity_exists_index_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index a6e64ba56..8a631cbd0 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -27,6 +27,7 @@ #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" #include "utils/age_global_graph.h" +#include "utils/ag_cache.h" #include "catalog/ag_graph.h" static void begin_cypher_set(CustomScanState *node, EState *estate, @@ -39,6 +40,9 @@ static void process_update_list(CustomScanState *node); static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple); +static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name); const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, begin_cypher_set, @@ -62,6 +66,14 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, Plan *subplan; Assert(list_length(css->cs->custom_plans) == 1); + css->graph_oid = get_graph_oid(css->set_list->graph_name); + if (!OidIsValid(css->graph_oid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + css->set_list->graph_name))); + } subplan = linitial(css->cs->custom_plans); node->ss.ps.lefttree = ExecInitNode(subplan, estate, eflags); @@ -91,9 +103,34 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + init_update_caches(&css->qual_cache, &css->index_cache, + "set_qual_cache", "set_index_cache"); + Increment_Estate_CommandId(estate); } +static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name) +{ + HASHCTL hashctl; + HASHCTL idx_hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(RLSCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + *qual_cache = hash_create(qual_name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); + idx_hashctl.keysize = sizeof(Oid); + idx_hashctl.entrysize = sizeof(IndexCacheEntry); + idx_hashctl.hcxt = CurrentMemoryContext; + *index_cache = hash_create(index_name, 8, &idx_hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple) @@ -388,27 +425,36 @@ void apply_update_list(CustomScanState *node, int *luindex = NULL; int lidx = 0; HTAB *qual_cache = NULL; - HASHCTL hashctl; HTAB *index_cache = NULL; - HASHCTL idx_hashctl; + bool local_caches = false; + Oid graph_oid; - /* allocate an array to hold the last update index of each 'entity' */ - luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid); + if (node->methods == &cypher_set_exec_methods) + { + cypher_set_custom_scan_state *css = + (cypher_set_custom_scan_state *)node; - /* Hash table for caching compiled security quals per label */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(Oid); - hashctl.entrysize = sizeof(RLSCacheEntry); - hashctl.hcxt = CurrentMemoryContext; - qual_cache = hash_create("update_qual_cache", 8, &hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + qual_cache = css->qual_cache; + index_cache = css->index_cache; + graph_oid = css->graph_oid; + } + else + { + init_update_caches(&qual_cache, &index_cache, + "update_qual_cache", "update_index_cache"); + local_caches = true; + graph_oid = get_graph_oid(set_info->graph_name); + if (!OidIsValid(graph_oid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + set_info->graph_name))); + } + } - MemSet(&idx_hashctl, 0, sizeof(idx_hashctl)); - idx_hashctl.keysize = sizeof(Oid); - idx_hashctl.entrysize = sizeof(IndexCacheEntry); - idx_hashctl.hcxt = CurrentMemoryContext; - index_cache = hash_create("update_index_cache", 8, &idx_hashctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + /* allocate an array to hold the last update index of each 'entity' */ + luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid); /* * Iterate through the SET items list and store the loop index of each @@ -441,10 +487,12 @@ void apply_update_list(CustomScanState *node, agtype_value *original_properties; agtype_value *id; agtype_value *label; + agtype_value *startid = NULL; + agtype_value *endid = NULL; agtype *original_entity; agtype *new_property_value = NULL; - TupleTableSlot *slot; - ResultRelInfo *resultRelInfo; + TupleTableSlot *slot = NULL; + ResultRelInfo *resultRelInfo = NULL; ScanKeyData scan_keys[1]; TableScanDesc scan_desc; bool remove_property; @@ -455,10 +503,12 @@ void apply_update_list(CustomScanState *node, char *clause_name = set_info->clause_name; int cid; Oid index_oid = InvalidOid; - Relation rel; - Oid relid; + Relation rel = NULL; + Oid relid = InvalidOid; IndexCacheEntry *idx_entry; bool found_idx_entry; + RLSCacheEntry *rls_entry = NULL; + bool found_rls_entry; update_item = (cypher_update_item *)lfirst(lc); @@ -587,81 +637,28 @@ void apply_update_list(CustomScanState *node, } } - resultRelInfo = create_entity_result_rel_info( - estate, set_info->graph_name, label_name); - - rel = resultRelInfo->ri_RelationDesc; - relid = RelationGetRelid(rel); - - idx_entry = hash_search(index_cache, &relid, HASH_ENTER, &found_idx_entry); - if (!found_idx_entry) - { - /* Check if there is a valid index on the 'id' column */ - idx_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); - } - index_oid = idx_entry->index_oid; - - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - - /* Setup RLS policies if RLS is enabled */ - if (check_enable_rls(resultRelInfo->ri_RelationDesc->rd_id, - InvalidOid, true) == RLS_ENABLED) - { - RLSCacheEntry *entry; - bool found; - - /* Get cached RLS state for this label, or set it up */ - entry = hash_search(qual_cache, &relid, HASH_ENTER, &found); - if (!found) - { - /* Setup WITH CHECK policies */ - setup_wcos(resultRelInfo, estate, node, CMD_UPDATE); - entry->withCheckOptions = resultRelInfo->ri_WithCheckOptions; - entry->withCheckOptionExprs = resultRelInfo->ri_WithCheckOptionExprs; - - /* Setup security quals */ - entry->qualExprs = setup_security_quals(resultRelInfo, estate, - node, CMD_UPDATE); - entry->slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - } - else - { - /* Use cached WCOs */ - resultRelInfo->ri_WithCheckOptions = entry->withCheckOptions; - resultRelInfo->ri_WithCheckOptionExprs = entry->withCheckOptionExprs; - } - } - /* * Now that we have the updated properties, create a either a vertex or - * edge Datum for the in-memory update, and setup the tupleTableSlot - * for the on-disc update. + * edge Datum for the in-memory update. The tupleTableSlot and relation + * metadata for the on-disc update are only needed for the last update + * targeting this entity. */ if (original_entity_value->type == AGTV_VERTEX) { new_entity = make_vertex(GRAPHID_GET_DATUM(id->val.int_value), CStringGetDatum(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); - - slot = populate_vertex_tts(slot, id, altered_properties); } else if (original_entity_value->type == AGTV_EDGE) { - agtype_value *startid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "start_id"); - agtype_value *endid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "end_id"); + startid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "start_id"); + endid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "end_id"); new_entity = make_edge(GRAPHID_GET_DATUM(id->val.int_value), GRAPHID_GET_DATUM(startid->val.int_value), GRAPHID_GET_DATUM(endid->val.int_value), CStringGetDatum(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); - - slot = populate_edge_tts(slot, id, startid, endid, - altered_properties); } else { @@ -690,6 +687,81 @@ void apply_update_list(CustomScanState *node, if (luindex[update_item->entity_position - 1] == lidx) { + label_cache_data *label_cache; + + label_cache = search_label_graph_oid_cache( + graph_oid, GET_LABEL_ID(id->val.int_value)); + if (label_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" does not exist", label_name))); + } + + resultRelInfo = create_entity_result_rel_info_by_oid( + estate, label_cache->relation); + + rel = resultRelInfo->ri_RelationDesc; + relid = RelationGetRelid(rel); + + idx_entry = hash_search(index_cache, &relid, HASH_ENTER, + &found_idx_entry); + if (!found_idx_entry) + { + /* Check if there is a valid index on the 'id' column. */ + idx_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + } + index_oid = idx_entry->index_oid; + + slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), + &TTSOpsHeapTuple); + + rls_entry = hash_search(qual_cache, &relid, HASH_ENTER, + &found_rls_entry); + if (!found_rls_entry) + { + rls_entry->rls_enabled = + check_enable_rls(resultRelInfo->ri_RelationDesc->rd_id, + InvalidOid, true) == RLS_ENABLED; + + if (rls_entry->rls_enabled) + { + /* Setup WITH CHECK policies. */ + setup_wcos(resultRelInfo, estate, node, CMD_UPDATE); + rls_entry->withCheckOptions = + resultRelInfo->ri_WithCheckOptions; + rls_entry->withCheckOptionExprs = + resultRelInfo->ri_WithCheckOptionExprs; + + /* Setup security quals. */ + rls_entry->qualExprs = setup_security_quals(resultRelInfo, + estate, node, + CMD_UPDATE); + rls_entry->slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), + &TTSOpsHeapTuple); + } + } + else if (rls_entry->rls_enabled) + { + /* Use cached WCOs. */ + resultRelInfo->ri_WithCheckOptions = + rls_entry->withCheckOptions; + resultRelInfo->ri_WithCheckOptionExprs = + rls_entry->withCheckOptionExprs; + } + + if (original_entity_value->type == AGTV_VERTEX) + { + slot = populate_vertex_tts(slot, id, altered_properties); + } + else + { + slot = populate_edge_tts(slot, id, startid, endid, + altered_properties); + } + if (OidIsValid(index_oid)) { Relation index_rel; @@ -722,23 +794,12 @@ void apply_update_list(CustomScanState *node, HeapTuple original_tuple = heap_tuple; /* Check RLS security quals (USING policy) before update */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - - /* Entry was already created earlier when setting up WCOs */ - entry = hash_search(qual_cache, &relid, HASH_FIND, NULL); - if (!entry) - { - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("missing RLS cache entry for relation %u", - relid))); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - should_update = check_security_quals(entry->qualExprs, - entry->slot, + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, + false); + should_update = check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext); } @@ -786,23 +847,11 @@ void apply_update_list(CustomScanState *node, bool should_update = true; /* Check RLS security quals (USING policy) before update */ - if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED) + if (rls_entry->rls_enabled) { - RLSCacheEntry *entry; - - /* Entry was already created earlier when setting up WCOs */ - entry = hash_search(qual_cache, &relid, HASH_FIND, NULL); - if (!entry) - { - ereport(ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("missing RLS cache entry for relation %u", - relid))); - } - - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - should_update = check_security_quals(entry->qualExprs, - entry->slot, + ExecStoreHeapTuple(heap_tuple, rls_entry->slot, false); + should_update = check_security_quals(rls_entry->qualExprs, + rls_entry->slot, econtext); } @@ -816,20 +865,23 @@ void apply_update_list(CustomScanState *node, /* close the ScanDescription */ table_endscan(scan_desc); } + + /* close relation */ + ExecCloseIndices(resultRelInfo); + table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); } estate->es_snapshot->curcid = cid; - /* close relation */ - ExecCloseIndices(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); /* increment loop index */ lidx++; } - /* Clean up the cache */ - hash_destroy(qual_cache); - hash_destroy(index_cache); + if (local_caches) + { + hash_destroy(qual_cache); + hash_destroy(index_cache); + } /* free our lookup array */ pfree_if_not_null(luindex); @@ -875,7 +927,7 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) CommandCounterIncrement(); /* invalidate VLE cache — graph was mutated */ - increment_graph_version(get_graph_oid(css->set_list->graph_name)); + increment_graph_version(css->graph_oid); return NULL; } @@ -886,7 +938,7 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) CommandCounterIncrement(); /* invalidate VLE cache — graph was mutated */ - increment_graph_version(get_graph_oid(css->set_list->graph_name)); + increment_graph_version(css->graph_oid); estate->es_result_relations = saved_resultRels; @@ -897,6 +949,20 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) static void end_cypher_set(CustomScanState *node) { + cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; + + if (css->qual_cache != NULL) + { + hash_destroy(css->qual_cache); + css->qual_cache = NULL; + } + + if (css->index_cache != NULL) + { + hash_destroy(css->index_cache); + css->index_cache = NULL; + } + ExecEndNode(node->ss.ps.lefttree); } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index 8237cdcce..87f80cfbe 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -24,17 +24,16 @@ #include "postgres.h" +#include "access/tableam.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/makefuncs.h" -#include "parser/parse_relation.h" #include "rewrite/rewriteManip.h" #include "rewrite/rowsecurity.h" #include "utils/acl.h" #include "utils/rls.h" #include "catalog/ag_label.h" -#include "commands/label_commands.h" #include "executor/cypher_utils.h" #include "utils/ag_cache.h" @@ -54,67 +53,18 @@ static void sort_policies_by_name(List *policies); static int row_security_policy_cmp(const ListCell *a, const ListCell *b); static bool check_role_for_policy(ArrayType *policy_roles, Oid user_id); -/* - * Given the graph name and the label name, create a ResultRelInfo for the table - * those two variables represent. Open the Indices too. - */ -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name) +ResultRelInfo *create_entity_result_rel_info_by_oid(EState *estate, Oid relid) { - RangeVar *rv = NULL; - Relation label_relation = NULL; - ResultRelInfo *resultRelInfo = NULL; - ParseState *pstate = NULL; - RangeTblEntry *rte = NULL; - int pii = 0; - - /* create a new parse state for this operation */ - pstate = make_parsestate(NULL); + Relation label_relation; + ResultRelInfo *resultRelInfo; + label_relation = table_open(relid, RowExclusiveLock); resultRelInfo = palloc(sizeof(ResultRelInfo)); - if (strlen(label_name) == 0) - { - rv = makeRangeVar(graph_name, AG_DEFAULT_LABEL_VERTEX, -1); - } - else - { - rv = makeRangeVar(graph_name, label_name, -1); - } - - label_relation = parserOpenTable(pstate, rv, RowExclusiveLock); - - /* - * Get the rte to determine the correct perminfoindex value. Some rtes - * may have it set up, some created here (executor) may not. - * - * Note: The RTEPermissionInfo structure was added in PostgreSQL version 16. - * - * Note: We use the list_length because exec_rt_fetch starts at 1, not 0. - * Doing this gives us the last rte in the es_range_table list, which - * is the rte in question. - * - * If the rte is created here and doesn't have a perminfoindex, we - * need to pass on a 0. Otherwise, later on GetResultRTEPermissionInfo - * will attempt to get the rte's RTEPermissionInfo data, which doesn't - * exist. - * - * TODO: Ideally, we should consider creating the RTEPermissionInfo data, - * but as this is just a read of the label relation, it is likely - * unnecessary. - */ - rte = exec_rt_fetch(list_length(estate->es_range_table), estate); - pii = (rte->perminfoindex == 0) ? 0 : list_length(estate->es_range_table); - - /* initialize the resultRelInfo */ - InitResultRelInfo(resultRelInfo, label_relation, pii, NULL, + InitResultRelInfo(resultRelInfo, label_relation, 0, NULL, estate->es_instrument); - - /* open the indices */ ExecOpenIndices(resultRelInfo, false); - free_parsestate(pstate); - return resultRelInfo; } @@ -196,11 +146,30 @@ TupleTableSlot *populate_edge_tts( } +HTAB *create_entity_exists_index_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(IndexCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +bool entity_exists(EState *estate, Oid graph_oid, graphid id) +{ + return entity_exists_with_cache(estate, graph_oid, id, NULL); +} + /* * Find out if the entity still exists. This is for 'implicit' deletion * of an entity. */ -bool entity_exists(EState *estate, Oid graph_oid, graphid id) +bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, + HTAB *index_cache) { label_cache_data *label; ScanKeyData scan_keys[1]; @@ -239,9 +208,25 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) estate->es_snapshot->curcid = Max(saved_curcid, GetCurrentCommandId(false)); - rel = table_open(label->relation, RowExclusiveLock); + rel = table_open(label->relation, AccessShareLock); + + if (index_cache != NULL) + { + IndexCacheEntry *entry; + bool found; - index_oid = find_usable_btree_index_for_attr(rel, 1); + entry = hash_search(index_cache, &label->relation, HASH_ENTER, + &found); + if (!found) + { + entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + } + index_oid = entry->index_oid; + } + else + { + index_oid = find_usable_btree_index_for_attr(rel, 1); + } if (OidIsValid(index_oid)) { @@ -281,7 +266,7 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) table_endscan(scan_desc); } - table_close(rel, RowExclusiveLock); + table_close(rel, AccessShareLock); /* Restore the original curcid */ estate->es_snapshot->curcid = saved_curcid; diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 5e4344254..577a763a0 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -25,12 +25,14 @@ #include "optimizer/cypher_pathnode.h" #include "parser/cypher_analyze.h" #include "executor/cypher_utils.h" +#include "optimizer/optimizer.h" #include "optimizer/subselect.h" #include "nodes/makefuncs.h" static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private); static bool expr_has_sublink(Node *node, void *context); +static void apply_child_path_costs(CustomPath *cp, RelOptInfo *rel); const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; @@ -60,9 +62,7 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - cp->path.rows = 0; /* Basic CREATE will not return rows */ - cp->path.startup_cost = 0; /* Basic CREATE will not fetch any pages */ - cp->path.total_cost = 0; + apply_child_path_costs(cp, rel); /* No output ordering for basic CREATE */ cp->path.pathkeys = NULL; @@ -96,9 +96,7 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - cp->path.rows = 0; /* Basic SET will not return rows */ - cp->path.startup_cost = 0; /* Basic SET will not fetch any pages */ - cp->path.total_cost = 0; + apply_child_path_costs(cp, rel); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -136,9 +134,7 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; + apply_child_path_costs(cp, rel); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -179,9 +175,7 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - cp->path.rows = 0; - cp->path.startup_cost = 0; - cp->path.total_cost = 0; + apply_child_path_costs(cp, rel); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -268,3 +262,41 @@ static bool expr_has_sublink(Node *node, void *context) return cypher_expr_tree_walker(node, expr_has_sublink, context); } + +static void apply_child_path_costs(CustomPath *cp, RelOptInfo *rel) +{ + Path *best_child = NULL; + ListCell *lc; + + foreach (lc, rel->pathlist) + { + Path *child = (Path *)lfirst(lc); + + if (best_child == NULL || + child->total_cost < best_child->total_cost || + (child->total_cost == best_child->total_cost && + child->startup_cost < best_child->startup_cost)) + best_child = child; + } + + if (best_child == NULL) + { + cp->path.rows = 0; + cp->path.startup_cost = 0; + cp->path.total_cost = 0; + return; + } + + /* + * The custom DML node still performs the graph-specific mutation, but its + * child paths are PostgreSQL-planned scans/subqueries. Preserve their + * estimates so upstream planning can see row counts, index selectivity, + * and scan costs instead of treating every Cypher DML clause as free. + * Charge one cpu_tuple_cost per input row for the mutation coordination + * work that happens above the child plan. + */ + cp->path.rows = best_child->rows; + cp->path.startup_cost = best_child->startup_cost; + cp->path.total_cost = best_child->total_cost + + cpu_tuple_cost * best_child->rows; +} diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 3083c52e1..31512c22b 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -25,6 +25,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/tableam.h" #include "catalog/pg_aggregate.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" @@ -406,6 +407,24 @@ add_rte_permissions(ParseState *pstate, Oid relid, AclMode permissions) relid, permissions); } +static Relation +open_label_relation(cypher_parsestate *cpstate, const char *label_name, + int location) +{ + Oid relid; + + relid = get_label_relation(label_name, cpstate->graph_oid); + if (!OidIsValid(relid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" does not exist", label_name), + parser_errposition(&cpstate->pstate, location))); + } + + return table_open(relid, RowExclusiveLock); +} + /* * Add required permissions to the label table for a given entity variable. * Looks up the entity by variable name, extracts its label, and adds @@ -6512,8 +6531,7 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, } /* lock the relation of the label */ - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + label_relation = open_label_relation(cpstate, edge->label, edge->location); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -6790,8 +6808,7 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + label_relation = open_label_relation(cpstate, node->label, node->location); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8052,8 +8069,7 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } /* lock the relation of the label */ - rv = makeRangeVar(cpstate->graph_name, edge->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + label_relation = open_label_relation(cpstate, edge->label, edge->location); /* * TODO @@ -8189,8 +8205,7 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; - rv = makeRangeVar(cpstate->graph_name, node->label, -1); - label_relation = parserOpenTable(&cpstate->pstate, rv, RowExclusiveLock); + label_relation = open_label_relation(cpstate, node->label, node->location); /* * TODO diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index a9b9b7111..cbfedb2fb 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -19,6 +19,7 @@ #include "postgres.h" +#include "access/genam.h" #include "access/heapam.h" #include "catalog/namespace.h" #include "commands/trigger.h" @@ -121,6 +122,12 @@ typedef struct edge_entry graphid end_vertex_id; /* end vertex */ } edge_entry; +typedef struct graph_label_entry +{ + char *name; + Oid relation; +} graph_label_entry; + /* * GRAPH global context per graph. They are chained together via next. * Be aware that the global pointer will point to the root BUT that @@ -281,12 +288,11 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, char label_type) { List *labels = NIL; - ScanKeyData scan_keys[2]; + ScanKeyData scan_key; Relation ag_label; - TableScanDesc scan_desc; + SysScanDesc scan_desc; HeapTuple tuple; TupleDesc tupdesc; - Oid index_oid = InvalidOid; /* we need a valid snapshot */ Assert(snapshot != NULL); @@ -299,110 +305,43 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, /* bail if the number of columns differs - this table has 5 */ Assert(tupdesc->natts == Natts_ag_label); - /* - * Find a usable index whose first key column is ag_label.graph - * (Anum_ag_label_graph) - */ - index_oid = find_usable_btree_index_for_attr(ag_label, Anum_ag_label_graph); - - if (OidIsValid(index_oid)) - { - Relation index_rel; - IndexScanDesc idx_scan_desc; - ScanKeyData key; - TupleTableSlot *slot; - - index_rel = index_open(index_oid, AccessShareLock); - slot = table_slot_create(ag_label, NULL); + ScanKeyInit(&scan_key, Anum_ag_label_graph, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(graph_oid)); - /* - * Setup ScanKey: ag_label.graph = graph_oid - * Note: We CANNOT filter by 'kind' here because it is not in the index. - */ - ScanKeyInit(&key, 1, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); + scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), + true, snapshot, 1, &scan_key); - idx_scan_desc = index_beginscan(ag_label, index_rel, snapshot, NULL, 1, 0); - index_rescan(idx_scan_desc, &key, 1, NULL, 0); + while (HeapTupleIsValid(tuple = systable_getnext(scan_desc))) + { + graph_label_entry *label; + bool is_null; + Datum datum; - while (index_getnext_slot(idx_scan_desc, ForwardScanDirection, slot)) + datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); + if (is_null || DatumGetChar(datum) != label_type) { - bool shouldFree; - - tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); - - if (HeapTupleIsValid(tuple)) - { - bool is_null; - Datum kind_datum; - - /* - * Since the index only gave us rows for the correct graph, - * we must now check if the label 'kind' matches (vertex 'v' or edge 'e'). - */ - kind_datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); - - if (!is_null && DatumGetChar(kind_datum) == label_type) - { - Datum name_datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); - if (!is_null) - { - Name label_name_ptr; - Name lval; - - label_name_ptr = DatumGetName(name_datum); - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label_name_ptr)); - labels = lappend(labels, lval); - } - } - } - - if (shouldFree) - { - heap_freetuple(tuple); - } - ExecClearTuple(slot); + continue; } - ExecDropSingleTupleTableSlot(slot); - index_endscan(idx_scan_desc); - index_close(index_rel, AccessShareLock); - } - else - { - /* setup scan keys to get all edges for the given graph oid */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - ScanKeyInit(&scan_keys[0], Anum_ag_label_kind, BTEqualStrategyNumber, - F_CHAREQ, CharGetDatum(label_type)); - - scan_desc = table_beginscan(ag_label, snapshot, 2, scan_keys); - - /* get all of the label names */ - while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) + datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); + if (!is_null) { - Name label; - Name lval; - bool is_null = false; + Name label_name; - /* something is wrong if this tuple isn't valid */ - Assert(HeapTupleIsValid(tuple)); - /* get the label name */ - label = DatumGetName(heap_getattr(tuple, Anum_ag_label_name, tupdesc, - &is_null)); + label = palloc(sizeof(*label)); + label_name = DatumGetName(datum); + label->name = pstrdup(NameStr(*label_name)); + datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, + &is_null); Assert(!is_null); - /* add it to our list */ - lval = (Name) palloc(NAMEDATALEN); - namestrcpy(lval, NameStr(*label)); - labels = lappend(labels, lval); - } + label->relation = DatumGetObjectId(datum); - /* close up scan */ - table_endscan(scan_desc); + labels = lappend(labels, label); + } } + systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); return labels; @@ -623,34 +562,30 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, static void load_vertex_hashtable(GRAPH_global_context *ggctx) { Oid graph_oid; - Oid graph_namespace_oid; Snapshot snapshot; - List *vertex_label_names = NIL; + List *vertex_labels = NIL; ListCell *lc; /* get the specific graph OID and namespace (schema) OID */ graph_oid = ggctx->graph_oid; - graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); /* get the active snapshot */ snapshot = GetActiveSnapshot(); - /* get the names of all of the vertex label tables */ - vertex_label_names = get_ag_labels_names(snapshot, graph_oid, - LABEL_TYPE_VERTEX); + /* get metadata for all vertex label tables */ + vertex_labels = get_ag_labels_names(snapshot, graph_oid, LABEL_TYPE_VERTEX); /* go through all vertex label tables in list */ - foreach (lc, vertex_label_names) + foreach (lc, vertex_labels) { Relation graph_vertex_label; TableScanDesc scan_desc; HeapTuple tuple; + graph_label_entry *label_entry; char *vertex_label_name; Oid vertex_label_table_oid; TupleDesc tupdesc; - /* get the vertex label name */ - vertex_label_name = lfirst(lc); - /* get the vertex label name's OID */ - vertex_label_table_oid = get_relname_relid(vertex_label_name, - graph_namespace_oid); + label_entry = lfirst(lc); + vertex_label_name = label_entry->name; + vertex_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); scan_desc = table_beginscan(graph_vertex_label, snapshot, 0, NULL); @@ -725,34 +660,30 @@ static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx) static void load_edge_hashtable(GRAPH_global_context *ggctx) { Oid graph_oid; - Oid graph_namespace_oid; Snapshot snapshot; - List *edge_label_names = NIL; + List *edge_labels = NIL; ListCell *lc; /* get the specific graph OID and namespace (schema) OID */ graph_oid = ggctx->graph_oid; - graph_namespace_oid = get_namespace_oid(ggctx->graph_name, false); /* get the active snapshot */ snapshot = GetActiveSnapshot(); - /* get the names of all of the edge label tables */ - edge_label_names = get_ag_labels_names(snapshot, graph_oid, - LABEL_TYPE_EDGE); + /* get metadata for all edge label tables */ + edge_labels = get_ag_labels_names(snapshot, graph_oid, LABEL_TYPE_EDGE); /* go through all edge label tables in list */ - foreach (lc, edge_label_names) + foreach (lc, edge_labels) { Relation graph_edge_label; TableScanDesc scan_desc; HeapTuple tuple; + graph_label_entry *label_entry; char *edge_label_name; Oid edge_label_table_oid; TupleDesc tupdesc; - /* get the edge label name */ - edge_label_name = lfirst(lc); - /* get the edge label name's OID */ - edge_label_table_oid = get_relname_relid(edge_label_name, - graph_namespace_oid); + label_entry = lfirst(lc); + edge_label_name = label_entry->name; + edge_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_edge_label = table_open(edge_label_table_oid, AccessShareLock); scan_desc = table_beginscan(graph_edge_label, snapshot, 0, NULL); diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 6700be3f3..0d717b84f 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -47,10 +47,12 @@ #include "parser/parse_coerce.h" #include "nodes/nodes.h" #include "utils/acl.h" +#include "utils/ag_cache.h" #include "utils/builtins.h" #include "executor/cypher_utils.h" #include "utils/float.h" #include "utils/lsyscache.h" +#include "utils/relcache.h" #include "utils/snapmgr.h" #include "utils/typcache.h" #include "utils/age_vle.h" @@ -159,9 +161,10 @@ static bool is_object_vertex(agtype_value *agtv); static bool is_object_edge(agtype_value *agtv); static bool is_array_path(agtype_value *agtv); /* graph entity retrieval */ -static Datum get_vertex(const char *graph, const char *vertex_label, +static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid); -static char *get_label_name(const char *graph_name, graphid element_graphid); +static char *get_label_name(const char *graph_name, graphid element_graphid, + Oid *label_relation); static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, bool *is_null); static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, @@ -5676,62 +5679,27 @@ Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, * node or edge. The function returns a pointer to a duplicated string that * needs to be freed when you are finished using it. */ -static char *get_label_name(const char *graph_name, graphid element_graphid) +static char *get_label_name(const char *graph_name, graphid element_graphid, + Oid *label_relation) { - ScanKeyData scan_keys[2]; - Relation ag_label; - SysScanDesc scan_desc; - HeapTuple tuple; - TupleDesc tupdesc; - char *result = NULL; - bool column_is_null = false; Oid graph_oid = get_graph_oid(graph_name); int32 label_id = get_graphid_label_id(element_graphid); + label_cache_data *label_cache; - /* scankey for first match in ag_label, column 2, graphoid, BTEQ, OidEQ */ - ScanKeyInit(&scan_keys[0], Anum_ag_label_graph, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(graph_oid)); - /* scankey for second match in ag_label, column 3, label id, BTEQ, Int4EQ */ - ScanKeyInit(&scan_keys[1], Anum_ag_label_id, BTEqualStrategyNumber, - F_INT4EQ, Int32GetDatum(label_id)); - - ag_label = table_open(ag_label_relation_id(), ShareLock); - scan_desc = systable_beginscan(ag_label, ag_label_graph_oid_index_id(), true, - NULL, 2, scan_keys); - - tuple = systable_getnext(scan_desc); - if (!HeapTupleIsValid(tuple)) + label_cache = search_label_graph_oid_cache(graph_oid, label_id); + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graphid %lu does not exist", element_graphid))); } - /* get the tupdesc - we don't need to release this one */ - tupdesc = RelationGetDescr(ag_label); + *label_relation = label_cache->relation; - /* bail if the number of columns differs */ - if (tupdesc->natts != Natts_ag_label) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid number of attributes for ag_catalog.ag_label"))); - } - - /* get the label name */ - result = NameStr(*DatumGetName(heap_getattr(tuple, Anum_ag_label_name, - tupdesc, &column_is_null))); - /* duplicate it */ - result = pstrdup(result); - - /* end the scan and close the relation */ - systable_endscan(scan_desc); - table_close(ag_label, ShareLock); - - return result; + return pstrdup(NameStr(label_cache->name)); } -static Datum get_vertex(const char *graph, const char *vertex_label, +static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid) { ScanKeyData scan_keys[1]; @@ -5741,15 +5709,10 @@ static Datum get_vertex(const char *graph, const char *vertex_label, TupleDesc tupdesc; Datum id, properties, result; AclResult aclresult; - TupleTableSlot *slot; + TupleTableSlot *slot = NULL; Oid index_oid; bool should_free_tuple = false; - /* get the specific graph namespace (schema) */ - Oid graph_namespace_oid = get_namespace_oid(graph, false); - /* get the specific vertex label table (schema.vertex_label) */ - Oid vertex_label_table_oid = get_relname_relid(vertex_label, - graph_namespace_oid); /* get the active snapshot */ Snapshot snapshot = GetActiveSnapshot(); @@ -5762,16 +5725,17 @@ static Datum get_vertex(const char *graph, const char *vertex_label, } /* open the relation (table) */ - graph_vertex_label = table_open(vertex_label_table_oid, ShareLock); + graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); - index_oid = find_usable_btree_index_for_attr(graph_vertex_label, 1); + index_oid = find_usable_btree_index_for_attr(graph_vertex_label, + Anum_ag_label_vertex_table_id); if (OidIsValid(index_oid)) { IndexScanDesc index_scan_desc; Relation index_rel; - index_rel = index_open(index_oid, ShareLock); + index_rel = index_open(index_oid, AccessShareLock); slot = table_slot_create(graph_vertex_label, NULL); /* initialize the scan key using GRAPHIDEQ for index */ @@ -5784,13 +5748,11 @@ static Datum get_vertex(const char *graph, const char *vertex_label, if (index_getnext_slot(index_scan_desc, ForwardScanDirection, slot)) { - tuple = ExecCopySlotHeapTuple(slot); - should_free_tuple = true; + tuple = ExecFetchSlotHeapTuple(slot, true, &should_free_tuple); } index_endscan(index_scan_desc); - index_close(index_rel, ShareLock); - ExecDropSingleTupleTableSlot(slot); + index_close(index_rel, AccessShareLock); } else { @@ -5809,7 +5771,11 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { table_endscan(scan_desc); } - table_close(graph_vertex_label, ShareLock); + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } + table_close(graph_vertex_label, AccessShareLock); ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("graphid %lu does not exist", graphid))); @@ -5826,8 +5792,12 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { heap_freetuple(tuple); } + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } - table_close(graph_vertex_label, ShareLock); + table_close(graph_vertex_label, AccessShareLock); ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("access to vertex %lu denied by row-level security policy on \"%s\"", @@ -5840,8 +5810,7 @@ static Datum get_vertex(const char *graph, const char *vertex_label, if (tupdesc->natts != 2) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid number of attributes for %s.%s", graph, - vertex_label ))); + errmsg("Invalid number of attributes for %s", vertex_label))); /* get the id */ id = column_get_datum(tupdesc, tuple, 0, "id", GRAPHIDOID, true); @@ -5861,8 +5830,12 @@ static Datum get_vertex(const char *graph, const char *vertex_label, { heap_freetuple(tuple); } + if (slot != NULL) + { + ExecDropSingleTupleTableSlot(slot); + } - table_close(graph_vertex_label, ShareLock); + table_close(graph_vertex_label, AccessShareLock); /* return the vertex datum */ return result; } @@ -5876,6 +5849,7 @@ Datum age_startnode(PG_FUNCTION_ARGS) agtype_value *agtv_value = NULL; char *graph_name = NULL; char *label_name = NULL; + Oid label_relation = InvalidOid; graphid start_id; Datum result; @@ -5921,11 +5895,12 @@ Datum age_startnode(PG_FUNCTION_ARGS) start_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, start_id); + label_name = get_label_name(graph_name, start_id, &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); + Assert(OidIsValid(label_relation)); - result = get_vertex(graph_name, label_name, start_id); + result = get_vertex(label_name, label_relation, start_id); return result; } @@ -5939,6 +5914,7 @@ Datum age_endnode(PG_FUNCTION_ARGS) agtype_value *agtv_value = NULL; char *graph_name = NULL; char *label_name = NULL; + Oid label_relation = InvalidOid; graphid end_id; Datum result; @@ -5984,11 +5960,12 @@ Datum age_endnode(PG_FUNCTION_ARGS) end_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, end_id); + label_name = get_label_name(graph_name, end_id, &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); + Assert(OidIsValid(label_relation)); - result = get_vertex(graph_name, label_name, end_id); + result = get_vertex(label_name, label_relation, end_id); return result; } @@ -12228,17 +12205,41 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, */ Oid find_usable_btree_index_for_attr(Relation rel, AttrNumber attnum) { -List *index_list = RelationGetIndexList(rel); + List *index_list; ListCell *ilc; Oid index_oid = InvalidOid; + if (attnum == 1) + { + Oid pk_index_oid = RelationGetPrimaryKeyIndex(rel, true); + + if (OidIsValid(pk_index_oid)) + { + Relation pk_index_rel = index_open(pk_index_oid, AccessShareLock); + + if (pk_index_rel->rd_index->indisvalid && + pk_index_rel->rd_index->indnkeyatts >= 1 && + pk_index_rel->rd_index->indkey.values[0] == attnum && + pk_index_rel->rd_rel->relam == BTREE_AM_OID && + RelationGetIndexPredicate(pk_index_rel) == NIL) + { + index_close(pk_index_rel, AccessShareLock); + return pk_index_oid; + } + + index_close(pk_index_rel, AccessShareLock); + } + } + + index_list = RelationGetIndexList(rel); + foreach(ilc, index_list) { Oid curr_idx_oid = lfirst_oid(ilc); Relation curr_idx_rel = index_open(curr_idx_oid, AccessShareLock); if (curr_idx_rel->rd_index->indisvalid && - curr_idx_rel->rd_index->indnatts >= 1 && + curr_idx_rel->rd_index->indnkeyatts >= 1 && curr_idx_rel->rd_index->indkey.values[0] == attnum && curr_idx_rel->rd_rel->relam == BTREE_AM_OID && RelationGetIndexPredicate(curr_idx_rel) == NIL) diff --git a/src/include/catalog/ag_label.h b/src/include/catalog/ag_label.h index 0a8480b1a..37377b485 100644 --- a/src/include/catalog/ag_label.h +++ b/src/include/catalog/ag_label.h @@ -65,6 +65,12 @@ #define LABEL_KIND_VERTEX 'v' #define LABEL_KIND_EDGE 'e' +typedef struct ag_label_info +{ + char *name; + Oid relation; +} ag_label_info; + void insert_label(const char *label_name, Oid graph_oid, int32 label_id, char label_kind, Oid label_relation, const char *seq_name); void delete_label(Oid relation); @@ -80,7 +86,7 @@ bool label_id_exists(Oid graph_oid, int32 label_id); RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *label_name); -List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid); +List *get_all_edge_labels_per_graph(Snapshot snapshot, Oid graph_oid); #define label_exists(label_name, label_graph) \ OidIsValid(get_label_id(label_name, label_graph)) diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index ac4b5ea5a..8f6cd03c7 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -57,6 +57,7 @@ typedef struct cypher_create_custom_scan_state uint32 flags; TupleTableSlot *slot; Oid graph_oid; + HTAB *entity_exists_index_cache; } cypher_create_custom_scan_state; typedef struct cypher_set_custom_scan_state @@ -64,6 +65,9 @@ typedef struct cypher_set_custom_scan_state CustomScanState css; CustomScan *cs; cypher_update_information *set_list; + HTAB *qual_cache; + HTAB *index_cache; + Oid graph_oid; int flags; } cypher_set_custom_scan_state; @@ -92,6 +96,8 @@ typedef struct cypher_delete_custom_scan_state * and end_id column. */ HTAB *vertex_id_htab; + HTAB *qual_cache; + HTAB *index_cache; } cypher_delete_custom_scan_state; typedef struct cypher_merge_custom_scan_state @@ -108,11 +114,13 @@ typedef struct cypher_merge_custom_scan_state bool found_a_path; CommandId base_currentCommandId; struct created_path *created_paths_list; + int path_length; List *eager_tuples; - int eager_tuples_index; + ListCell *eager_tuples_cursor; bool eager_buffer_filled; cypher_update_information *on_match_set_info; /* NULL if not specified */ cypher_update_information *on_create_set_info; /* NULL if not specified */ + HTAB *entity_exists_index_cache; } cypher_merge_custom_scan_state; /* Reusable SET logic callable from MERGE executor */ @@ -125,11 +133,13 @@ TupleTableSlot *populate_edge_tts( TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *startid, agtype_value *endid, agtype_value *properties); -ResultRelInfo *create_entity_result_rel_info(EState *estate, char *graph_name, - char *label_name); +ResultRelInfo *create_entity_result_rel_info_by_oid(EState *estate, Oid relid); void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info); +HTAB *create_entity_exists_index_cache(const char *name); bool entity_exists(EState *estate, Oid graph_oid, graphid id); +bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, + HTAB *index_cache); HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate); @@ -150,6 +160,7 @@ bool check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd); typedef struct RLSCacheEntry { Oid relid; /* hash key */ + bool rls_enabled; /* Security quals (USING policies) for UPDATE/DELETE */ List *qualExprs; TupleTableSlot *slot; /* slot for old tuple (RLS check) */ From 661370fc3fe695915db4fddd79eabf24e1424b11 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:37 +0900 Subject: [PATCH 03/15] Share label and function lookup caches Reuse label metadata and Cypher function OIDs across parser, planner, load, VLE, and write-clause paths. This groups the first wave of label-cache plumbing with the function lookup caches that consume the same graph metadata. Parser relation lookups, MATCH label RTE construction, batch insert, graph generation, SET, DELETE, and VLE edge label handling all consult the shared caches instead of repeating catalog scans or name-based lookups. --- src/backend/executor/cypher_create.c | 35 ++-- src/backend/executor/cypher_delete.c | 69 +++++-- src/backend/executor/cypher_merge.c | 33 ++-- src/backend/executor/cypher_set.c | 88 ++++++--- src/backend/executor/cypher_utils.c | 88 ++++++++- src/backend/nodes/cypher_copyfuncs.c | 1 + src/backend/nodes/cypher_outfuncs.c | 2 +- src/backend/nodes/cypher_readfuncs.c | 1 + src/backend/optimizer/cypher_pathnode.c | 32 +-- src/backend/optimizer/cypher_paths.c | 32 ++- src/backend/parser/cypher_analyze.c | 32 ++- src/backend/parser/cypher_clause.c | 239 ++++++++++++++--------- src/backend/parser/cypher_expr.c | 13 +- src/backend/utils/adt/age_global_graph.c | 85 ++++++-- src/backend/utils/adt/age_vle.c | 18 +- src/backend/utils/adt/agtype.c | 70 ++----- src/backend/utils/ag_func.c | 80 ++++++-- src/backend/utils/cache/ag_cache.c | 86 ++++++-- src/backend/utils/graph_generation.c | 60 ++++-- src/backend/utils/load/ag_load_edges.c | 71 ++++++- src/backend/utils/load/ag_load_labels.c | 7 +- src/backend/utils/load/age_load.c | 69 ++++--- src/include/executor/cypher_utils.h | 27 +++ src/include/nodes/cypher_nodes.h | 1 + src/include/utils/age_global_graph.h | 2 + src/include/utils/agtype.h | 2 - src/include/utils/load/age_load.h | 3 +- 27 files changed, 872 insertions(+), 374 deletions(-) diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index dbda0da2e..bc44086f1 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -65,6 +65,7 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, (cypher_create_custom_scan_state *)node; ListCell *lc; Plan *subplan; + int result_rtindex; Assert(list_length(css->cs->custom_plans) == 1); @@ -84,6 +85,10 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } + result_rtindex = list_length(estate->es_range_table); + css->result_rel_info_cache = + create_entity_result_rel_info_cache("create_result_rel_info_cache"); + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); @@ -97,17 +102,10 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, if (!CYPHER_TARGET_NODE_INSERT_ENTITY(cypher_node->flags)) continue; - /* Open relation and acquire a row exclusive lock. */ - rel = table_open(cypher_node->relid, RowExclusiveLock); - - /* Initialize resultRelInfo for the vertex */ - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - /* Open all indexes for the relation */ - ExecOpenIndices(cypher_node->resultRelInfo, false); + cypher_node->resultRelInfo = get_entity_result_rel_info( + estate, css->result_rel_info_cache, cypher_node->relid); + rel = cypher_node->resultRelInfo->ri_RelationDesc; + cypher_node->resultRelInfo->ri_RangeTableIndex = result_rtindex; /* Setup the relation's tuple slot */ cypher_node->elemTupleSlot = table_slot_create( @@ -279,7 +277,7 @@ static void end_cypher_create(CustomScanState *node) if (css->entity_exists_index_cache != NULL) { - hash_destroy(css->entity_exists_index_cache); + destroy_entity_exists_index_cache(css->entity_exists_index_cache); css->entity_exists_index_cache = NULL; } @@ -298,14 +296,15 @@ static void end_cypher_create(CustomScanState *node) continue; } - /* close all indices for the node */ - ExecCloseIndices(cypher_node->resultRelInfo); - - /* close the relation itself */ - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); + cypher_node->resultRelInfo = NULL; } } + + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } } static void rescan_cypher_create(CustomScanState *node) diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index a354b6b18..f15b6c31a 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -215,6 +215,12 @@ static void end_cypher_delete(CustomScanState *node) css->index_cache = NULL; } + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } + hash_destroy(((cypher_delete_custom_scan_state *)node)->vertex_id_htab); ExecEndNode(node->ss.ps.lefttree); @@ -238,6 +244,9 @@ static void init_delete_caches(cypher_delete_custom_scan_state *css) idx_hashctl.hcxt = CurrentMemoryContext; css->index_cache = hash_create("delete_index_cache", 8, &idx_hashctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + css->result_rel_info_cache = + create_entity_result_rel_info_cache("delete_result_rel_info_cache"); } /* @@ -420,12 +429,11 @@ static void process_delete_list(CustomScanState *node) foreach(lc, css->delete_data->delete_items) { cypher_delete_item *item; - agtype_value *original_entity_value, *id, *label; + agtype_value *original_entity_value, *id; ScanKeyData scan_keys[1]; TableScanDesc scan_desc = NULL; ResultRelInfo *resultRelInfo; HeapTuple heap_tuple = NULL; - char *label_name; label_cache_data *label_cache; Integer *pos; int entity_position; @@ -455,19 +463,19 @@ static void process_delete_list(CustomScanState *node) entity_position); id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "label"); - label_name = pnstrdup(label->val.string.val, label->val.string.len); label_cache = search_label_graph_oid_cache(css->delete_data->graph_oid, GET_LABEL_ID(id->val.int_value)); if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("label \"%s\" does not exist", label_name))); + errmsg("label id %lu does not exist", + GET_LABEL_ID(id->val.int_value)))); } - resultRelInfo = create_entity_result_rel_info_by_oid( - estate, label_cache->relation); + resultRelInfo = get_entity_result_rel_info(estate, + css->result_rel_info_cache, + label_cache->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); @@ -495,11 +503,17 @@ static void process_delete_list(CustomScanState *node) errmsg("DELETE clause can only delete vertices and edges"))); } - idx_entry = hash_search(index_cache, &relid, HASH_ENTER, &found_idx_entry); - + idx_entry = hash_search(index_cache, &relid, HASH_ENTER, + &found_idx_entry); if (!found_idx_entry) + { + init_index_cache_entry(idx_entry); + } + + if (!idx_entry->index_oid_cached) { idx_entry->index_oid = find_usable_btree_index_for_attr(rel, id_attr_num); + idx_entry->index_oid_cached = true; } index_oid = idx_entry->index_oid; @@ -598,7 +612,6 @@ static void process_delete_list(CustomScanState *node) table_endscan(scan_desc); } - destroy_entity_result_rel_info(resultRelInfo); } } @@ -786,20 +799,41 @@ static void check_for_connected_edges(CustomScanState *node) bool delete_acl_checked = false; List *qualExprs = NIL; ExprContext *econtext = NULL; - Oid start_index_oid = InvalidOid; - Oid end_index_oid = InvalidOid; + Oid start_index_oid; + Oid end_index_oid; Relation rel; + IndexCacheEntry *idx_entry; + bool found_idx_entry; - resultRelInfo = create_entity_result_rel_info_by_oid( - estate, label_info->relation); + resultRelInfo = get_entity_result_rel_info(estate, + css->result_rel_info_cache, + label_info->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); estate->es_snapshot->curcid = GetCurrentCommandId(false); estate->es_output_cid = GetCurrentCommandId(false); - /* Look for indexes on start_id and end_id columns. */ - start_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_start_id); - end_index_oid = find_usable_btree_index_for_attr(rel, Anum_ag_label_edge_table_end_id); + idx_entry = hash_search(css->index_cache, &relid, HASH_ENTER, + &found_idx_entry); + if (!found_idx_entry) + { + init_index_cache_entry(idx_entry); + } + if (!idx_entry->start_index_oid_cached) + { + idx_entry->start_index_oid = find_usable_btree_index_for_attr( + rel, Anum_ag_label_edge_table_start_id); + idx_entry->start_index_oid_cached = true; + } + if (!idx_entry->end_index_oid_cached) + { + idx_entry->end_index_oid = find_usable_btree_index_for_attr( + rel, Anum_ag_label_edge_table_end_id); + idx_entry->end_index_oid_cached = true; + } + + start_index_oid = idx_entry->start_index_oid; + end_index_oid = idx_entry->end_index_oid; if (OidIsValid(start_index_oid) && OidIsValid(end_index_oid)) { @@ -914,6 +948,5 @@ static void check_for_connected_edges(CustomScanState *node) table_endscan(scan_desc); } - destroy_entity_result_rel_info(resultRelInfo); } } diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 19b679a7f..84b149ff1 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -109,6 +109,7 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, (cypher_merge_custom_scan_state *)node; ListCell *lc = NULL; Plan *subplan = NULL; + int result_rtindex; css->created_paths_list = NULL; Assert(list_length(css->cs->custom_plans) == 1); @@ -135,6 +136,10 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } + result_rtindex = list_length(estate->es_range_table); + css->result_rel_info_cache = + create_entity_result_rel_info_cache("merge_result_rel_info_cache"); + /* * For each vertex and edge in the path, setup the information * needed if we need to create them. @@ -156,17 +161,10 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, continue; } - /* Open relation and acquire a row exclusive lock. */ - rel = table_open(cypher_node->relid, RowExclusiveLock); - - /* Initialize resultRelInfo for the vertex */ - cypher_node->resultRelInfo = makeNode(ResultRelInfo); - InitResultRelInfo(cypher_node->resultRelInfo, rel, - list_length(estate->es_range_table), NULL, - estate->es_instrument); - - /* Open all indexes for the relation */ - ExecOpenIndices(cypher_node->resultRelInfo, false); + cypher_node->resultRelInfo = get_entity_result_rel_info( + estate, css->result_rel_info_cache, cypher_node->relid); + rel = cypher_node->resultRelInfo->ri_RelationDesc; + cypher_node->resultRelInfo->ri_RangeTableIndex = result_rtindex; /* Setup the relation's tuple slot */ cypher_node->elemTupleSlot = ExecInitExtraTupleSlot( @@ -1063,7 +1061,7 @@ static void end_cypher_merge(CustomScanState *node) if (css->entity_exists_index_cache != NULL) { - hash_destroy(css->entity_exists_index_cache); + destroy_entity_exists_index_cache(css->entity_exists_index_cache); css->entity_exists_index_cache = NULL; } @@ -1076,12 +1074,13 @@ static void end_cypher_merge(CustomScanState *node) continue; } - /* close all indices for the node */ - ExecCloseIndices(cypher_node->resultRelInfo); + cypher_node->resultRelInfo = NULL; + } - /* close the relation itself */ - table_close(cypher_node->resultRelInfo->ri_RelationDesc, - RowExclusiveLock); + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; } /* free up our created paths lists */ diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 8a631cbd0..22290be40 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -24,6 +24,7 @@ #include "storage/bufmgr.h" #include "utils/rls.h" +#include "commands/label_commands.h" #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" #include "utils/age_global_graph.h" @@ -41,8 +42,9 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple); static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, - const char *qual_name, - const char *index_name); + HTAB **result_rel_info_cache, + const char *qual_name, const char *index_name, + const char *result_rel_info_name); const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, begin_cypher_set, @@ -104,14 +106,17 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, } init_update_caches(&css->qual_cache, &css->index_cache, - "set_qual_cache", "set_index_cache"); + &css->result_rel_info_cache, + "set_qual_cache", "set_index_cache", + "set_result_rel_info_cache"); Increment_Estate_CommandId(estate); } static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, - const char *qual_name, - const char *index_name) + HTAB **result_rel_info_cache, + const char *qual_name, const char *index_name, + const char *result_rel_info_name) { HASHCTL hashctl; HASHCTL idx_hashctl; @@ -129,6 +134,9 @@ static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, idx_hashctl.hcxt = CurrentMemoryContext; *index_cache = hash_create(index_name, 8, &idx_hashctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + *result_rel_info_cache = + create_entity_result_rel_info_cache(result_rel_info_name); } static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, @@ -426,6 +434,7 @@ void apply_update_list(CustomScanState *node, int lidx = 0; HTAB *qual_cache = NULL; HTAB *index_cache = NULL; + HTAB *result_rel_info_cache = NULL; bool local_caches = false; Oid graph_oid; @@ -436,20 +445,27 @@ void apply_update_list(CustomScanState *node, qual_cache = css->qual_cache; index_cache = css->index_cache; + result_rel_info_cache = css->result_rel_info_cache; graph_oid = css->graph_oid; } else { init_update_caches(&qual_cache, &index_cache, - "update_qual_cache", "update_index_cache"); + &result_rel_info_cache, + "update_qual_cache", "update_index_cache", + "update_result_rel_info_cache"); local_caches = true; - graph_oid = get_graph_oid(set_info->graph_name); + graph_oid = set_info->graph_oid; if (!OidIsValid(graph_oid)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_SCHEMA), - errmsg("graph \"%s\" does not exist", - set_info->graph_name))); + graph_oid = get_graph_oid(set_info->graph_name); + if (!OidIsValid(graph_oid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + set_info->graph_name))); + } } } @@ -486,7 +502,7 @@ void apply_update_list(CustomScanState *node, agtype_value *original_entity_value; agtype_value *original_properties; agtype_value *id; - agtype_value *label; + label_cache_data *label_cache; agtype_value *startid = NULL; agtype_value *endid = NULL; agtype *original_entity; @@ -541,11 +557,23 @@ void apply_update_list(CustomScanState *node, clause_name))); } - /* get the id and label for later */ + /* get the id and label metadata for later */ id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "label"); + label_cache = search_label_graph_oid_cache( + graph_oid, GET_LABEL_ID(id->val.int_value)); + if (label_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label id %lu does not exist", + GET_LABEL_ID(id->val.int_value)))); + } + label_name = NameStr(label_cache->name); + if (IS_AG_DEFAULT_LABEL(label_name)) + { + label_name = ""; + } - label_name = pnstrdup(label->val.string.val, label->val.string.len); /* get the properties we need to update */ original_properties = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "properties"); @@ -687,19 +715,8 @@ void apply_update_list(CustomScanState *node, if (luindex[update_item->entity_position - 1] == lidx) { - label_cache_data *label_cache; - - label_cache = search_label_graph_oid_cache( - graph_oid, GET_LABEL_ID(id->val.int_value)); - if (label_cache == NULL) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("label \"%s\" does not exist", label_name))); - } - - resultRelInfo = create_entity_result_rel_info_by_oid( - estate, label_cache->relation); + resultRelInfo = get_entity_result_rel_info( + estate, result_rel_info_cache, label_cache->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); @@ -707,9 +724,14 @@ void apply_update_list(CustomScanState *node, idx_entry = hash_search(index_cache, &relid, HASH_ENTER, &found_idx_entry); if (!found_idx_entry) + { + init_index_cache_entry(idx_entry); + } + if (!idx_entry->index_oid_cached) { /* Check if there is a valid index on the 'id' column. */ idx_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + idx_entry->index_oid_cached = true; } index_oid = idx_entry->index_oid; @@ -866,9 +888,6 @@ void apply_update_list(CustomScanState *node, table_endscan(scan_desc); } - /* close relation */ - ExecCloseIndices(resultRelInfo); - table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock); } estate->es_snapshot->curcid = cid; @@ -881,6 +900,7 @@ void apply_update_list(CustomScanState *node, { hash_destroy(qual_cache); hash_destroy(index_cache); + destroy_entity_result_rel_info_cache(result_rel_info_cache); } /* free our lookup array */ @@ -963,6 +983,12 @@ static void end_cypher_set(CustomScanState *node) css->index_cache = NULL; } + if (css->result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->result_rel_info_cache); + css->result_rel_info_cache = NULL; + } + ExecEndNode(node->ss.ps.lefttree); } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index 87f80cfbe..414e7cc42 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -25,6 +25,7 @@ #include "postgres.h" #include "access/tableam.h" +#include "common/hashfn.h" #include "executor/executor.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -37,6 +38,12 @@ #include "executor/cypher_utils.h" #include "utils/ag_cache.h" +typedef struct EntityResultRelInfoCacheEntry +{ + Oid relid; + ResultRelInfo *resultRelInfo; +} EntityResultRelInfoCacheEntry; + /* RLS helper function declarations */ static void get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, List **permissive_policies, @@ -78,6 +85,50 @@ void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info) table_close(result_rel_info->ri_RelationDesc, RowExclusiveLock); } +HTAB *create_entity_result_rel_info_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(Oid); + hashctl.entrysize = sizeof(EntityResultRelInfoCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +ResultRelInfo *get_entity_result_rel_info(EState *estate, + HTAB *result_rel_info_cache, + Oid relid) +{ + EntityResultRelInfoCacheEntry *entry; + bool found; + + entry = hash_search(result_rel_info_cache, &relid, HASH_ENTER, &found); + if (!found) + { + entry->resultRelInfo = create_entity_result_rel_info_by_oid(estate, + relid); + } + + return entry->resultRelInfo; +} + +void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache) +{ + HASH_SEQ_STATUS hash_seq; + EntityResultRelInfoCacheEntry *entry; + + hash_seq_init(&hash_seq, result_rel_info_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + destroy_entity_result_rel_info(entry->resultRelInfo); + } + + hash_destroy(result_rel_info_cache); +} + TupleTableSlot *populate_vertex_tts( TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties) { @@ -159,6 +210,23 @@ HTAB *create_entity_exists_index_cache(const char *name) HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } +void destroy_entity_exists_index_cache(HTAB *index_cache) +{ + HASH_SEQ_STATUS hash_seq; + IndexCacheEntry *entry; + + hash_seq_init(&hash_seq, index_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->rel != NULL) + { + table_close(entry->rel, AccessShareLock); + } + } + + hash_destroy(index_cache); +} + bool entity_exists(EState *estate, Oid graph_oid, graphid id) { return entity_exists_with_cache(estate, graph_oid, id, NULL); @@ -208,8 +276,6 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, estate->es_snapshot->curcid = Max(saved_curcid, GetCurrentCommandId(false)); - rel = table_open(label->relation, AccessShareLock); - if (index_cache != NULL) { IndexCacheEntry *entry; @@ -219,12 +285,21 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, &found); if (!found) { + init_index_cache_entry(entry); + entry->rel = table_open(label->relation, AccessShareLock); + rel = entry->rel; entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + entry->index_oid_cached = true; + } + else + { + rel = entry->rel; } index_oid = entry->index_oid; } else { + rel = table_open(label->relation, AccessShareLock); index_oid = find_usable_btree_index_for_attr(rel, 1); } @@ -266,7 +341,10 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, table_endscan(scan_desc); } - table_close(rel, AccessShareLock); + if (index_cache == NULL) + { + table_close(rel, AccessShareLock); + } /* Restore the original curcid */ estate->es_snapshot->curcid = saved_curcid; @@ -640,7 +718,7 @@ add_with_check_options(Relation rel, wco->polname = NULL; wco->cascaded = false; - if (list_length(permissive_quals) == 1) + if (lnext(permissive_quals, list_head(permissive_quals)) == NULL) { wco->qual = (Node *) linitial(permissive_quals); } @@ -844,7 +922,7 @@ add_security_quals(int rt_index, * Then add a single security qual combining together the USING * clauses from all the permissive policies using OR. */ - if (list_length(permissive_quals) == 1) + if (lnext(permissive_quals, list_head(permissive_quals)) == NULL) { rowsec_expr = (Expr *) linitial(permissive_quals); } diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index 283096ca7..9747c2603 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -121,6 +121,7 @@ void copy_cypher_update_information(ExtensibleNode *newnode, const ExtensibleNod COPY_SCALAR_FIELD(flags); COPY_SCALAR_FIELD(tuple_position); COPY_STRING_FIELD(graph_name); + COPY_SCALAR_FIELD(graph_oid); COPY_STRING_FIELD(clause_name); } diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index 84d32a8f8..51e06bc5d 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -425,6 +425,7 @@ void out_cypher_update_information(StringInfo str, const ExtensibleNode *node) WRITE_INT32_FIELD(flags); WRITE_INT32_FIELD(tuple_position); WRITE_STRING_FIELD(graph_name); + WRITE_INT32_FIELD(graph_oid); WRITE_STRING_FIELD(clause_name); } @@ -494,4 +495,3 @@ outChar(StringInfo str, char c) outToken(str, in); } - diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 1e7e0ef82..1d687afc3 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -251,6 +251,7 @@ void read_cypher_update_information(struct ExtensibleNode *node) READ_UINT_FIELD(flags); READ_INT_FIELD(tuple_position); READ_STRING_FIELD(graph_name); + READ_UINT_FIELD(graph_oid); READ_STRING_FIELD(clause_name); } diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 577a763a0..4d9e96f41 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -32,7 +32,8 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private); static bool expr_has_sublink(Node *node, void *context); -static void apply_child_path_costs(CustomPath *cp, RelOptInfo *rel); +static Path *select_best_child_path(RelOptInfo *rel); +static void apply_child_path_costs(CustomPath *cp, Path *best_child); const CustomPathMethods cypher_create_path_methods = { CREATE_PATH_NAME, plan_cypher_create_path, NULL}; @@ -62,7 +63,8 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - apply_child_path_costs(cp, rel); + cp->custom_paths = list_make1(select_best_child_path(rel)); + apply_child_path_costs(cp, linitial(cp->custom_paths)); /* No output ordering for basic CREATE */ cp->path.pathkeys = NULL; @@ -70,7 +72,6 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, /* Disable all custom flags for now */ cp->flags = 0; - cp->custom_paths = rel->pathlist; cp->custom_private = custom_private; cp->methods = &cypher_create_path_methods; @@ -96,7 +97,8 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - apply_child_path_costs(cp, rel); + cp->custom_paths = list_make1(select_best_child_path(rel)); + apply_child_path_costs(cp, linitial(cp->custom_paths)); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -104,7 +106,6 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, /* Disable all custom flags for now */ cp->flags = 0; - cp->custom_paths = rel->pathlist; cp->custom_private = custom_private; cp->methods = &cypher_set_path_methods; @@ -134,7 +135,8 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - apply_child_path_costs(cp, rel); + cp->custom_paths = list_make1(select_best_child_path(rel)); + apply_child_path_costs(cp, linitial(cp->custom_paths)); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -142,8 +144,6 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, /* Disable all custom flags for now */ cp->flags = 0; - /* Make the original paths the children of the new path */ - cp->custom_paths = rel->pathlist; /* Store the metadata Delete will need in the execution phase. */ cp->custom_private = custom_private; /* Tells Postgres how to turn this path to the correct CustomScan */ @@ -175,7 +175,8 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, cp->path.parallel_safe = false; cp->path.parallel_workers = 0; - apply_child_path_costs(cp, rel); + cp->custom_paths = list_make1(select_best_child_path(rel)); + apply_child_path_costs(cp, linitial(cp->custom_paths)); /* No output ordering for basic SET */ cp->path.pathkeys = NULL; @@ -183,9 +184,6 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, /* Disable all custom flags for now */ cp->flags = 0; - /* Make the original paths the children of the new path */ - cp->custom_paths = rel->pathlist; - /* * Store the metadata Merge will need in the execution phase. * We may have a sublink here in case the user used a list @@ -263,7 +261,7 @@ static bool expr_has_sublink(Node *node, void *context) return cypher_expr_tree_walker(node, expr_has_sublink, context); } -static void apply_child_path_costs(CustomPath *cp, RelOptInfo *rel) +static Path *select_best_child_path(RelOptInfo *rel) { Path *best_child = NULL; ListCell *lc; @@ -279,6 +277,14 @@ static void apply_child_path_costs(CustomPath *cp, RelOptInfo *rel) best_child = child; } + if (best_child == NULL) + elog(ERROR, "Cypher custom path requires a child path"); + + return best_child; +} + +static void apply_child_path_costs(CustomPath *cp, Path *best_child) +{ if (best_child == NULL) { cp->path.rows = 0; diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index 6c4fd7e07..0b567993a 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -37,9 +37,15 @@ typedef enum cypher_clause_kind static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook; +static Oid cypher_create_clause_func_oid = InvalidOid; +static Oid cypher_set_clause_func_oid = InvalidOid; +static Oid cypher_delete_clause_func_oid = InvalidOid; +static Oid cypher_merge_clause_func_oid = InvalidOid; + static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); +static void init_cypher_clause_function_oids(void); static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, @@ -113,19 +119,37 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) return CYPHER_CLAUSE_NONE; fe = (FuncExpr *)te->expr; + init_cypher_clause_function_oids(); - if (is_oid_ag_func(fe->funcid, CREATE_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_create_clause_func_oid) return CYPHER_CLAUSE_CREATE; - if (is_oid_ag_func(fe->funcid, SET_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_set_clause_func_oid) return CYPHER_CLAUSE_SET; - if (is_oid_ag_func(fe->funcid, DELETE_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_delete_clause_func_oid) return CYPHER_CLAUSE_DELETE; - if (is_oid_ag_func(fe->funcid, MERGE_CLAUSE_FUNCTION_NAME)) + if (fe->funcid == cypher_merge_clause_func_oid) return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; } +static void init_cypher_clause_function_oids(void) +{ + if (OidIsValid(cypher_create_clause_func_oid)) + { + return; + } + + cypher_create_clause_func_oid = + get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + cypher_set_clause_func_oid = + get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + cypher_delete_clause_func_oid = + get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + cypher_merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); +} + /* replace all possible paths with our CustomPath */ static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index fc5766858..b3a55edbe 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -54,6 +54,7 @@ static void post_parse_analyze(ParseState *pstate, Query *query, JumbleState *js static bool convert_cypher_walker(Node *node, ParseState *pstate); static bool is_rte_cypher(RangeTblEntry *rte); static bool is_func_cypher(FuncExpr *funcexpr); +static Oid get_cypher_func_oid(void); static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate); static Name expr_get_const_name(Node *expr); static const char *expr_get_const_cstring(Node *expr, const char *source_str); @@ -312,12 +313,14 @@ static bool is_rte_cypher(RangeTblEntry *rte) { RangeTblFunction *rtfunc; FuncExpr *funcexpr; + ListCell *first_function; /* * The planner expects RangeTblFunction nodes in rte->functions list. * We cannot replace one of them to a SELECT subquery. */ - if (list_length(rte->functions) != 1) + first_function = list_head(rte->functions); + if (first_function == NULL || lnext(rte->functions, first_function) != NULL) return false; /* @@ -369,7 +372,20 @@ static bool is_func_cypher(FuncExpr *funcexpr) return false; } - return is_oid_ag_func(funcexpr->funcid, "cypher"); + return funcexpr->funcid == get_cypher_func_oid(); +} + +static Oid get_cypher_func_oid(void) +{ + static Oid cypher_func_oid = InvalidOid; + + if (!OidIsValid(cypher_func_oid)) + { + cypher_func_oid = get_ag_func_oid("cypher", 3, NAMEOID, CSTRINGOID, + AGTYPEOID); + } + + return cypher_func_oid; } /* convert cypher() call to SELECT subquery in-place */ @@ -377,6 +393,8 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) { RangeTblFunction *rtfunc = linitial(rte->functions); FuncExpr *funcexpr = (FuncExpr *)rtfunc->funcexpr; + List *args = funcexpr->args; + int nargs = list_length(args); Node *arg1 = NULL; Node *arg2 = NULL; Node *arg3 = NULL; @@ -405,7 +423,7 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) } /* verify that we have 2 input parameters as it is possible to get 1 or 0 */ - if (funcexpr->args == NULL || list_length(funcexpr->args) < 2) + if (nargs < 2) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), @@ -414,8 +432,8 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) } /* get our first 2 arguments */ - arg1 = linitial(funcexpr->args); - arg2 = lsecond(funcexpr->args); + arg1 = linitial(args); + arg2 = lsecond(args); Assert(exprType(arg1) == NAMEOID); Assert(exprType(arg2) == CSTRINGOID); @@ -475,9 +493,9 @@ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) * Check to see if the cypher function had a third parameter passed to it, * if so make sure Postgres parsed the second argument to a Param node. */ - if (list_length(funcexpr->args) == 3) + if (nargs == 3) { - arg3 = lthird(funcexpr->args); + arg3 = lthird(args); if (!IsA(arg3, Param)) { ereport(ERROR, diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 31512c22b..a22ed2593 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -333,6 +333,7 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); +static Oid get_clause_function_oid(const char *function_name); static FuncExpr *make_clause_func_expr(char *function_name, Node *clause_information); static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); @@ -408,12 +409,16 @@ add_rte_permissions(ParseState *pstate, Oid relid, AclMode permissions) } static Relation -open_label_relation(cypher_parsestate *cpstate, const char *label_name, - int location) +open_label_relation_with_lock(cypher_parsestate *cpstate, + const char *label_name, int location, + LOCKMODE lockmode) { + label_cache_data *label_cache; Oid relid; - relid = get_label_relation(label_name, cpstate->graph_oid); + label_cache = search_label_name_graph_cache(label_name, + cpstate->graph_oid); + relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (!OidIsValid(relid)) { ereport(ERROR, @@ -422,7 +427,15 @@ open_label_relation(cypher_parsestate *cpstate, const char *label_name, parser_errposition(&cpstate->pstate, location))); } - return table_open(relid, RowExclusiveLock); + return table_open(relid, lockmode); +} + +static Relation +open_label_relation(cypher_parsestate *cpstate, const char *label_name, + int location) +{ + return open_label_relation_with_lock(cpstate, label_name, location, + RowExclusiveLock); } /* @@ -437,6 +450,7 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, ParseState *pstate = (ParseState *)cpstate; transform_entity *entity; char *label = NULL; + label_cache_data *label_cache; Oid relid; entity = find_variable(cpstate, var_name); @@ -459,7 +473,8 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, return; } - relid = get_label_relation(label, cpstate->graph_oid); + label_cache = search_label_name_graph_cache(label, cpstate->graph_oid); + relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (OidIsValid(relid)) { add_rte_permissions(pstate, relid, permissions); @@ -2078,6 +2093,7 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate, set_items_target_list->clause_name = clause_name; set_items_target_list->graph_name = cpstate->graph_name; + set_items_target_list->graph_oid = cpstate->graph_oid; if (!clause->next) { @@ -4247,7 +4263,7 @@ static List *join_to_entity(cypher_parsestate *cpstate, { List *edge_quals = make_edge_quals(cpstate, entity, side); - if (list_length(edge_quals) > 1) + if (lnext(edge_quals, list_head(edge_quals)) != NULL) { expr = makeSimpleA_Expr(AEXPR_IN, "=", qual, (Node *)edge_quals, -1); @@ -4460,7 +4476,7 @@ static Node *transform_map_to_ind(cypher_parsestate *cpstate, Assert(quals != NIL); - if (list_length(quals) > 1) + if (lnext(quals, list_head(quals)) != NULL) { return (Node *)makeBoolExpr(AND_EXPR, quals, -1); } @@ -5600,9 +5616,7 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; - char *schema_name = NULL; - char *rel_name = NULL; - RangeVar *label_range_var = NULL; + Relation label_relation = NULL; Alias *alias = NULL; int resno = -1; TargetEntry *te = NULL; @@ -5809,23 +5823,26 @@ static Expr *transform_cypher_edge(cypher_parsestate *cpstate, rel->name = get_next_default_alias(cpstate); } - schema_name = get_graph_namespace_name(cpstate->graph_name); - if (valid_label) { - rel_name = get_label_relation_name(rel->label, cpstate->graph_oid); + label_relation = open_label_relation_with_lock(cpstate, rel->label, + rel->location, + AccessShareLock); } else { - rel_name = AG_DEFAULT_LABEL_EDGE; + label_relation = open_label_relation_with_lock(cpstate, + AG_DEFAULT_LABEL_EDGE, + rel->location, + AccessShareLock); } - label_range_var = makeRangeVar(schema_name, rel_name, -1); alias = makeAlias(rel->name, NIL); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, - label_range_var->inh, true); + pnsi = addRangeTableEntryForRelation(pstate, label_relation, + AccessShareLock, alias, true, true); Assert(pnsi != NULL); + table_close(label_relation, NoLock); /* * relation is visible (r.a in expression works) but attributes in the @@ -5858,9 +5875,7 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, bool output_node, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; - char *schema_name = NULL; - char *rel_name = NULL; - RangeVar *label_range_var = NULL; + Relation label_relation = NULL; Alias *alias = NULL; int resno = -1; TargetEntry *te = NULL; @@ -6096,24 +6111,27 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, } /* now build a new vertex */ - schema_name = get_graph_namespace_name(cpstate->graph_name); - if (valid_label) { - rel_name = get_label_relation_name(node->label, cpstate->graph_oid); + label_relation = open_label_relation_with_lock(cpstate, node->label, + node->location, + AccessShareLock); } else { - rel_name = AG_DEFAULT_LABEL_VERTEX; + label_relation = open_label_relation_with_lock(cpstate, + AG_DEFAULT_LABEL_VERTEX, + node->location, + AccessShareLock); } - label_range_var = makeRangeVar(schema_name, rel_name, -1); alias = makeAlias(node->name, NIL); - pnsi = addRangeTableEntry(pstate, label_range_var, alias, - label_range_var->inh, true); + pnsi = addRangeTableEntryForRelation(pstate, label_relation, + AccessShareLock, alias, true, true); Assert(pnsi != NULL); + table_close(label_relation, NoLock); /* * relation is visible (r.a in expression works) but attributes in the @@ -6450,17 +6468,7 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, char *alias; AttrNumber resno; ParseNamespaceItem *pnsi; - - if (edge->label) - { - if (get_label_kind(edge->label, cpstate->graph_oid) == LABEL_KIND_VERTEX) - { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("label %s is for vertices, not edges", edge->label), - parser_errposition(pstate, edge->location))); - } - } + label_cache_data *label_cache = NULL; rel->type = LABEL_KIND_EDGE; rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; @@ -6516,8 +6524,18 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, parser_errposition(&cpstate->pstate, edge->location))); } + label_cache = search_label_name_graph_cache(edge->label, + cpstate->graph_oid); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("label %s is for vertices, not edges", edge->label), + parser_errposition(pstate, edge->location))); + } + /* create the label entry if it does not exist */ - if (!label_exists(edge->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; @@ -6592,10 +6610,13 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, cypher_node *node, bool has_edge) { ParseState *pstate = (ParseState *)cpstate; + label_cache_data *label_cache = NULL; if (node->label) { - if (get_label_kind(node->label, cpstate->graph_oid) == LABEL_KIND_EDGE) + label_cache = search_label_name_graph_cache(node->label, + cpstate->graph_oid); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("label %s is for edges, not vertices", @@ -6772,6 +6793,7 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, char *alias; int resno; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; rel->type = LABEL_KIND_VERTEX; rel->tuple_position = InvalidAttrNumber; @@ -6792,8 +6814,11 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->label_name = node->label; } + label_cache = search_label_name_graph_cache(node->label, + cpstate->graph_oid); + /* create the label entry if it does not exist */ - if (!label_exists(node->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; @@ -6986,25 +7011,25 @@ transform_cypher_clause_as_subquery(cypher_parsestate *cpstate, * NOTE: skip namespace conflicts check if the rte will be the only * RangeTblEntry in pstate */ - if (list_length(pstate->p_rtable) > 1) { - List *namespace = NULL; - int rtindex = 0; + int rtindex = list_length(pstate->p_rtable); - /* get the index of the last entry */ - rtindex = list_length(pstate->p_rtable); - - /* the rte at the end should be the rte just added */ - if (rte != rt_fetch(rtindex, pstate->p_rtable)) + if (rtindex > 1) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("rte must be last entry in p_rtable"))); - } + List *namespace = NULL; - namespace = list_make1(pnsi); + /* the rte at the end should be the rte just added */ + if (rte != rt_fetch(rtindex, pstate->p_rtable)) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("rte must be last entry in p_rtable"))); + } + + namespace = list_make1(pnsi); - checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + checkNameSpaceConflicts(pstate, pstate->p_namespace, namespace); + } } if (add_rte_to_query) @@ -7392,6 +7417,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, transform_cypher_set_item_list(cpstate, self->on_match, query); merge_information->on_match_set_info->clause_name = "MERGE ON MATCH SET"; merge_information->on_match_set_info->graph_name = cpstate->graph_name; + merge_information->on_match_set_info->graph_oid = cpstate->graph_oid; resolve_merge_set_exprs( merge_information->on_match_set_info->set_items, @@ -7405,6 +7431,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, transform_cypher_set_item_list(cpstate, self->on_create, query); merge_information->on_create_set_info->clause_name = "MERGE ON CREATE SET"; merge_information->on_create_set_info->graph_name = cpstate->graph_name; + merge_information->on_create_set_info->graph_oid = cpstate->graph_oid; resolve_merge_set_exprs( merge_information->on_create_set_info->set_items, @@ -8008,6 +8035,7 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, RangeVar *rv; RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; if (edge->name != NULL) { @@ -8050,8 +8078,17 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } + label_cache = search_label_name_graph_cache(edge->label, + cpstate->graph_oid); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Expecting edge label, found existing vertex label"), + parser_errposition(&cpstate->pstate, edge->location))); + } + /* check to see if the label exists, create the label entry if it does not. */ - if (edge->label && !label_exists(edge->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; /* @@ -8071,22 +8108,6 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, /* lock the relation of the label */ label_relation = open_label_relation(cpstate, edge->label, edge->location); - /* - * TODO - * It is possible for a vertex label to be retrieved, instead of an edge, - * due to the above logic. So, we need to check if it is a vertex label. - * This whole section needs to be fixed because it could be a relation that - * isn't either and has the correct number of columns. However, for now, - * we just check the number of columns. - */ - /* TODO temporarily hardcoded */ - if (label_relation->rd_att->natts == 2) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Expecting edge label, found existing vertex label"), - parser_errposition(&cpstate->pstate, edge->location))); - } - /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8122,6 +8143,7 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, RangeVar *rv; RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; + label_cache_data *label_cache = NULL; if (node->name != NULL) { @@ -8184,8 +8206,17 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->label_name = node->label; } + label_cache = search_label_name_graph_cache(node->label, + cpstate->graph_oid); + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Expecting vertex label, found existing edge label"), + parser_errposition(&cpstate->pstate, node->location))); + } + /* check to see if the label exists, create the label entry if it does not. */ - if (node->label && !label_exists(node->label, cpstate->graph_oid)) + if (label_cache == NULL) { List *parent; @@ -8207,22 +8238,6 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, label_relation = open_label_relation(cpstate, node->label, node->location); - /* - * TODO - * It is possible for an edge label to be retrieved, instead of a vertex, - * due to the above logic. So, we need to check if it is an edge label. - * This whole section needs to be fixed because it could be a relation that - * isn't either and has the correct number of columns. However, for now, - * we just check the number of columns. - */ - /* TODO temporarily hardcoded */ - if (label_relation->rd_att->natts == 4) - { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Expecting vertex label, found existing edge label"), - parser_errposition(&cpstate->pstate, node->location))); - } - /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8320,7 +8335,7 @@ static FuncExpr *make_clause_func_expr(char *function_name, clause_information_const = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); - func_oid = get_ag_func_oid(function_name, 1, INTERNALOID); + func_oid = get_clause_function_oid(function_name); func_expr = makeFuncExpr(func_oid, AGTYPEOID, list_make1(clause_information_const), InvalidOid, @@ -8329,6 +8344,45 @@ static FuncExpr *make_clause_func_expr(char *function_name, return func_expr; } +static Oid get_clause_function_oid(const char *function_name) +{ + static Oid create_clause_func_oid = InvalidOid; + static Oid set_clause_func_oid = InvalidOid; + static Oid delete_clause_func_oid = InvalidOid; + static Oid merge_clause_func_oid = InvalidOid; + + if (!OidIsValid(create_clause_func_oid)) + { + create_clause_func_oid = + get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + set_clause_func_oid = + get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + delete_clause_func_oid = + get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + if (strcmp(function_name, CREATE_CLAUSE_FUNCTION_NAME) == 0) + { + return create_clause_func_oid; + } + if (strcmp(function_name, SET_CLAUSE_FUNCTION_NAME) == 0) + { + return set_clause_func_oid; + } + if (strcmp(function_name, DELETE_CLAUSE_FUNCTION_NAME) == 0) + { + return delete_clause_func_oid; + } + if (strcmp(function_name, MERGE_CLAUSE_FUNCTION_NAME) == 0) + { + return merge_clause_func_oid; + } + + return get_ag_func_oid(function_name, 1, INTERNALOID); +} + /* * This function is borrowed from PG version 16.1. * @@ -8338,6 +8392,7 @@ static FuncExpr *make_clause_func_expr(char *function_name, static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) { int varno; + int nnullingrels; ListCell *lc; /* Note: we can't see FromExpr here */ @@ -8365,9 +8420,11 @@ static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex) * maintain the p_nullingrels list lazily, we might need to extend it to * make the varno'th entry exist. */ - while (list_length(pstate->p_nullingrels) < varno) + nnullingrels = list_length(pstate->p_nullingrels); + while (nnullingrels < varno) { pstate->p_nullingrels = lappend(pstate->p_nullingrels, NULL); + nnullingrels++; } lc = list_nth_cell(pstate->p_nullingrels, varno - 1); lfirst(lc) = bms_add_member((Bitmapset *) lfirst(lc), jindex); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 1ed777486..8ba66b43f 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -607,7 +607,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) * We need to check this before processing to avoid returning NULL result * which causes "cache lookup failed for type 0" error. */ - if (rexpr->elems == NIL || list_length((List *)rexpr->elems) == 0) + if (rexpr->elems == NIL) { Datum bool_value; Const *const_result; @@ -662,7 +662,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) * ScalarArrayOpExpr is only going to be useful if there's more than one * non-Var righthand item. */ - if (list_length(rnonvars) > 1) + if (rnonvars != NIL && lnext(rnonvars, list_head(rnonvars)) != NULL) { List *allexprs; Oid scalar_type; @@ -1764,15 +1764,16 @@ static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, { char *funcname = NameStr(procform->proname); int nargs = procform->pronargs; + int given_nargs = list_length(fargs); ListCell *lc = NULL; /* verify the length of args are same */ - if (list_length(fargs) != nargs) + if (given_nargs != nargs) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("function %s requires %d arguments, %d given", - funcname, nargs, list_length(fargs)))); + funcname, nargs, given_nargs))); } /* iterate through the function's args */ @@ -2019,7 +2020,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) Assert(!fn->agg_within_group); /* If it is a qualified function call, let it through. */ - if (list_length(fn->funcname) > 1) + if (lnext(fn->funcname, list_head(fn->funcname)) != NULL) { fname = fn->funcname; } @@ -2043,7 +2044,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * and vle. So, check for those 3 functions here and that the arg list * is not empty. Then prepend the graph name if necessary. */ - if ((list_length(targs) != 0) && + if ((targs != NIL) && (strcmp("startNode", name) == 0 || strcmp("endNode", name) == 0 || strcmp("vle", name) == 0 || diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index cbfedb2fb..6302f81ec 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -109,6 +109,7 @@ typedef struct vertex_entry ListGraphId *edges_out; /* List of exiting edges graphids (int64) */ ListGraphId *edges_self; /* List of selfloop edges graphids (int64) */ Oid vertex_label_table_oid; /* the label table oid */ + char *vertex_label_name; /* the label name */ ItemPointerData tid; /* physical tuple location for lazy fetch */ } vertex_entry; @@ -117,6 +118,7 @@ typedef struct edge_entry { graphid edge_id; /* edge id, it is also the hash key */ Oid edge_label_table_oid; /* the label table oid */ + char *edge_label_name; /* the label name */ ItemPointerData tid; /* physical tuple location for lazy fetch */ graphid start_vertex_id; /* start vertex */ graphid end_vertex_id; /* end vertex */ @@ -176,12 +178,14 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, char label_type); static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid); + graphid end_vertex_id, Oid edge_label_table_oid, + char *edge_label_name); static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, graphid edge_id, char *edge_label_name); static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, + char *vertex_label_name, ItemPointerData tid); /* definitions */ @@ -353,7 +357,8 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, */ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, - graphid end_vertex_id, Oid edge_label_table_oid) + graphid end_vertex_id, Oid edge_label_table_oid, + char *edge_label_name) { edge_entry *ee = NULL; bool found = false; @@ -402,6 +407,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ee->start_vertex_id = start_vertex_id; ee->end_vertex_id = end_vertex_id; ee->edge_label_table_oid = edge_label_table_oid; + ee->edge_label_name = pstrdup(edge_label_name); /* increment the number of loaded edges */ ggctx->num_loaded_edges++; @@ -415,6 +421,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, */ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, + char *vertex_label_name, ItemPointerData tid) { vertex_entry *ve = NULL; @@ -457,6 +464,7 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, ve->vertex_id = vertex_id; /* set the label table oid for this vertex */ ve->vertex_label_table_oid = vertex_label_table_oid; + ve->vertex_label_name = pstrdup(vertex_label_name); /* set the TID for lazy property fetch */ ve->tid = tid; /* set the NIL edge list */ @@ -603,6 +611,7 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL) { graphid vertex_id; + bool isnull; bool inserted = false; /* something is wrong if this isn't true */ @@ -612,13 +621,21 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) } Assert(HeapTupleIsValid(tuple)); - /* get the vertex id */ - vertex_id = DatumGetInt64(column_get_datum(tupdesc, tuple, 0, "id", - GRAPHIDOID, true)); + vertex_id = DatumGetInt64(heap_getattr(tuple, + Anum_ag_label_vertex_table_id, + tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex id is null for %s.%s", + ggctx->graph_name, vertex_label_name))); + } /* insert vertex into vertex hashtable with TID (no property copy) */ inserted = insert_vertex_entry(ggctx, vertex_id, vertex_label_table_oid, + vertex_label_name, tuple->t_self); /* warn if there is a duplicate */ @@ -703,6 +720,7 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) graphid edge_id; graphid edge_vertex_start_id; graphid edge_vertex_end_id; + bool isnull; bool inserted = false; /* something is wrong if this isn't true */ @@ -712,26 +730,43 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) } Assert(HeapTupleIsValid(tuple)); - /* get the edge id */ - edge_id = DatumGetInt64(column_get_datum(tupdesc, tuple, 0, "id", - GRAPHIDOID, true)); - /* get the edge start_id (start vertex id) */ - edge_vertex_start_id = DatumGetInt64(column_get_datum(tupdesc, - tuple, 1, - "start_id", - GRAPHIDOID, - true)); - /* get the edge end_id (end vertex id)*/ - edge_vertex_end_id = DatumGetInt64(column_get_datum(tupdesc, tuple, - 2, "end_id", - GRAPHIDOID, - true)); + edge_id = DatumGetInt64(heap_getattr(tuple, + Anum_ag_label_edge_table_id, + tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge id is null for %s.%s", + ggctx->graph_name, edge_label_name))); + } + + edge_vertex_start_id = DatumGetInt64(heap_getattr( + tuple, Anum_ag_label_edge_table_start_id, tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge start_id is null for %s.%s", + ggctx->graph_name, edge_label_name))); + } + + edge_vertex_end_id = DatumGetInt64(heap_getattr( + tuple, Anum_ag_label_edge_table_end_id, tupdesc, &isnull)); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge end_id is null for %s.%s", + ggctx->graph_name, edge_label_name))); + } /* insert edge into edge hashtable with TID (no property copy) */ inserted = insert_edge_entry(ggctx, edge_id, tuple->t_self, edge_vertex_start_id, edge_vertex_end_id, - edge_label_table_oid); + edge_label_table_oid, + edge_label_name); /* warn if there is a duplicate */ if (!inserted) @@ -1216,6 +1251,11 @@ Oid get_vertex_entry_label_table_oid(vertex_entry *ve) return ve->vertex_label_table_oid; } +char *get_vertex_entry_label_name(vertex_entry *ve) +{ + return ve->vertex_label_name; +} + /* * Fetch vertex properties on demand from the heap via stored TID. * @@ -1282,6 +1322,11 @@ Oid get_edge_entry_label_table_oid(edge_entry *ee) return ee->edge_label_table_oid; } +char *get_edge_entry_label_name(edge_entry *ee) +{ + return ee->edge_label_name; +} + /* * Fetch edge properties on demand from the heap via stored TID. * See get_vertex_entry_properties for memory and safety notes. diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 22c268cdf..6bd29d1c8 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -72,6 +72,7 @@ #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "nodes/cypher_nodes.h" +#include "utils/ag_cache.h" /* defines */ #define GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc) \ @@ -827,11 +828,15 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, if (agtv_temp->type == AGTV_STRING && agtv_temp->val.string.len != 0) { + label_cache_data *label_cache; + vlelctx->edge_label_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); - vlelctx->edge_label_name_oid = get_label_relation(vlelctx->edge_label_name, - graph_oid); + label_cache = search_label_name_graph_cache(vlelctx->edge_label_name, + graph_oid); + vlelctx->edge_label_name_oid = label_cache != NULL ? + label_cache->relation : InvalidOid; } else { @@ -1612,8 +1617,7 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) /* get the edge entry from the hashtable */ ee = get_edge_entry(ggctx, graphid_array[index]); - /* get the label name from the oid */ - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); + label_name = get_edge_entry_label_name(ee); /* reconstruct the edge */ agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, get_edge_entry_end_vertex_id(ee), @@ -1678,8 +1682,7 @@ static agtype_value *build_path(VLE_path_container *vpc) /* get the vertex entry from the hashtable */ ve = get_vertex_entry(ggctx, graphid_array[index]); - /* get the label name from the oid */ - label_name = get_rel_name(get_vertex_entry_label_table_oid(ve)); + label_name = get_vertex_entry_label_name(ve); /* reconstruct the vertex */ agtv_vertex = agtype_value_build_vertex(get_vertex_entry_id(ve), label_name, @@ -1699,8 +1702,7 @@ static agtype_value *build_path(VLE_path_container *vpc) /* get the edge entry from the hashtable */ ee = get_edge_entry(ggctx, graphid_array[index+1]); - /* get the label name from the oid */ - label_name = get_rel_name(get_edge_entry_label_table_oid(ee)); + label_name = get_edge_entry_label_name(ee); /* reconstruct the edge */ agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, get_edge_entry_end_vertex_id(ee), diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 0d717b84f..f98782de3 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -5627,53 +5627,6 @@ Datum age_end_id(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } -/* - * Helper function to return the Datum value of a column (attribute) in a heap - * tuple (row) given the column number (starting from 0), attribute name, typid, - * and whether it can be null. The function is designed to extract and validate - * that the data (attribute) is what is expected. The function will error on any - * issues. - */ -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, - const char *attname, Oid typid, bool isnull) -{ - Form_pg_attribute att; - HeapTupleHeader hth; - HeapTupleData tmptup, *htd; - Datum result; - bool _isnull = true; - - /* build the heap tuple data */ - hth = tuple->t_data; - tmptup.t_len = HeapTupleHeaderGetDatumLength(hth); - tmptup.t_data = hth; - htd = &tmptup; - - /* get the description for the column from the tuple descriptor */ - att = TupleDescAttr(tupdesc, column); - /* get the datum (attribute) for that column*/ - result = heap_getattr(htd, column + 1, tupdesc, &_isnull); - /* verify that the attribute typid is as expected */ - if (att->atttypid != typid) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute typid. Expected %d, found %d", typid, - att->atttypid))); - /* verify that the attribute name is as expected */ - if (strcmp(att->attname.data, attname) != 0) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Invalid attribute name. Expected %s, found %s", - attname, att->attname.data))); - /* verify that if it is null, it is allowed to be null */ - if (isnull == false && _isnull == true) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("Attribute was found to be null when null is not allowed."))); - - return result; -} - /* * Function to retrieve a label name, given the graph name and graphid of the * node or edge. The function returns a pointer to a duplicated string that @@ -5712,6 +5665,7 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, TupleTableSlot *slot = NULL; Oid index_oid; bool should_free_tuple = false; + bool isnull; /* get the active snapshot */ Snapshot snapshot = GetActiveSnapshot(); @@ -5812,11 +5766,23 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("Invalid number of attributes for %s", vertex_label))); - /* get the id */ - id = column_get_datum(tupdesc, tuple, 0, "id", GRAPHIDOID, true); - /* get the properties */ - properties = column_get_datum(tupdesc, tuple, 1, "properties", - AGTYPEOID, true); + id = heap_getattr(tuple, Anum_ag_label_vertex_table_id, tupdesc, &isnull); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex id is null for %s", vertex_label))); + } + + properties = heap_getattr(tuple, Anum_ag_label_vertex_table_properties, + tupdesc, &isnull); + if (isnull) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex properties are null for %s", vertex_label))); + } + /* reconstruct the vertex */ result = DirectFunctionCall3(_agtype_build_vertex, id, CStringGetDatum(vertex_label), properties); diff --git a/src/backend/utils/ag_func.c b/src/backend/utils/ag_func.c index 1762286c3..0d22699ab 100644 --- a/src/backend/utils/ag_func.c +++ b/src/backend/utils/ag_func.c @@ -27,12 +27,32 @@ #include "access/htup_details.h" #include "catalog/pg_proc.h" #include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/syscache.h" #include "catalog/ag_namespace.h" #include "utils/ag_func.h" +typedef struct ag_func_cache_key +{ + NameData name; + int nargs; + Oid args[FUNC_MAX_ARGS]; +} ag_func_cache_key; + +typedef struct ag_func_cache_entry +{ + ag_func_cache_key key; + Oid func_oid; +} ag_func_cache_entry; + +static HTAB *ag_func_oid_cache = NULL; + +static void initialize_ag_func_oid_cache(void); + /* checks that func_oid is of func_name function in ag_catalog */ bool is_oid_ag_func(Oid func_oid, const char *func_name) { @@ -64,33 +84,67 @@ bool is_oid_ag_func(Oid func_oid, const char *func_name) /* gets the function OID that matches with func_name and argument types */ Oid get_ag_func_oid(const char *func_name, const int nargs, ...) { - Oid oids[FUNC_MAX_ARGS]; + ag_func_cache_key key; + ag_func_cache_entry *entry; va_list ap; int i; - oidvector *arg_types; - Oid func_oid; + bool found; Assert(func_name); Assert(nargs >= 0 && nargs <= FUNC_MAX_ARGS); + initialize_ag_func_oid_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.name, func_name); + key.nargs = nargs; + va_start(ap, nargs); for (i = 0; i < nargs; i++) - oids[i] = va_arg(ap, Oid); + key.args[i] = va_arg(ap, Oid); va_end(ap); - arg_types = buildoidvector(oids, nargs); + entry = hash_search(ag_func_oid_cache, &key, HASH_ENTER, &found); + if (!found) + { + oidvector *arg_types; + + arg_types = buildoidvector(key.args, nargs); + entry->func_oid = GetSysCacheOid3( + PROCNAMEARGSNSP, Anum_pg_proc_oid, + CStringGetDatum(func_name), + PointerGetDatum(arg_types), + ObjectIdGetDatum(ag_catalog_namespace_id())); + if (!OidIsValid(entry->func_oid)) + { + ereport(ERROR, (errmsg_internal("ag function does not exist"), + errdetail_internal("%s(%d)", func_name, nargs))); + } + } - func_oid = GetSysCacheOid3(PROCNAMEARGSNSP, Anum_pg_proc_oid, - CStringGetDatum(func_name), - PointerGetDatum(arg_types), - ObjectIdGetDatum(ag_catalog_namespace_id())); - if (!OidIsValid(func_oid)) + return entry->func_oid; +} + +static void initialize_ag_func_oid_cache(void) +{ + HASHCTL hash_ctl; + + if (ag_func_oid_cache != NULL) { - ereport(ERROR, (errmsg_internal("ag function does not exist"), - errdetail_internal("%s(%d)", func_name, nargs))); + return; } - return func_oid; + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(ag_func_cache_key); + hash_ctl.entrysize = sizeof(ag_func_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + ag_func_oid_cache = hash_create("ag function oid cache", 32, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } Oid get_pg_func_oid(const char *func_name, const int nargs, ...) diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 493ffcfa9..e713b4744 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -127,6 +127,7 @@ static void flush_graph_name_cache(void); static void flush_graph_namespace_cache(void); static graph_cache_data *search_graph_name_cache_miss(Name name); static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace); +static void cache_graph_data_in_all_caches(const graph_cache_data *data); static void fill_graph_cache_data(graph_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc); @@ -163,6 +164,7 @@ static void *label_seq_name_graph_cache_hash_search(Name name, Oid graph, HASHACTION action, bool *found); +static void cache_label_data_in_all_caches(const label_cache_data *data); static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc); @@ -372,6 +374,7 @@ static graph_cache_data *search_graph_name_cache_miss(Name name) /* fill the new entry with the retrieved tuple */ fill_graph_cache_data(&entry->data, tuple, RelationGetDescr(ag_graph)); + cache_graph_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_graph, AccessShareLock); @@ -435,6 +438,7 @@ static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace) /* fill the new entry with the retrieved tuple */ fill_graph_cache_data(&entry->data, tuple, RelationGetDescr(ag_graph)); + cache_graph_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_graph, AccessShareLock); @@ -442,6 +446,27 @@ static graph_cache_data *search_graph_namespace_cache_miss(Oid namespace) return &entry->data; } +static void cache_graph_data_in_all_caches(const graph_cache_data *data) +{ + graph_name_cache_entry *name_entry; + graph_namespace_cache_entry *namespace_entry; + bool found; + + name_entry = hash_search(graph_name_cache_hash, &data->name, HASH_ENTER, + &found); + if (!found) + { + name_entry->data = *data; + } + + namespace_entry = hash_search(graph_namespace_cache_hash, &data->namespace, + HASH_ENTER, &found); + if (!found) + { + namespace_entry->data = *data; + } +} + static void fill_graph_cache_data(graph_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc) { @@ -482,18 +507,6 @@ static void initialize_label_caches(void) /* ag_label.relation */ ag_cache_scan_key_init(&label_relation_scan_keys[0], Anum_ag_label_relation, F_OIDEQ); - - /* ag_label.seq_name, ag_label.graph */ - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], Anum_ag_label_seq_name, - F_NAMEEQ); - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[1], Anum_ag_label_graph, - F_OIDEQ); - - /* ag_label.seq_name, ag_label.graph */ - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], - Anum_ag_label_seq_name, F_NAMEEQ); - ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[1], - Anum_ag_label_graph, F_OIDEQ); /* ag_label.seq_name, ag_label.graph */ ag_cache_scan_key_init(&label_seq_name_graph_scan_keys[0], @@ -855,6 +868,7 @@ static label_cache_data *search_label_name_graph_cache_miss(Name name, /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -932,6 +946,7 @@ static label_cache_data *search_label_graph_oid_cache_miss(Oid graph, uint32 id) /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -972,7 +987,7 @@ static label_cache_data *search_label_relation_cache_miss(Oid relation) SysScanDesc scan_desc; HeapTuple tuple; bool found; - label_cache_data *entry; + label_relation_cache_entry *entry; memcpy(scan_keys, label_relation_scan_keys, sizeof(label_relation_scan_keys)); @@ -1004,12 +1019,13 @@ static label_cache_data *search_label_relation_cache_miss(Oid relation) Assert(!found); /* no concurrent update on label_relation_cache_hash */ /* fill the new entry with the retrieved tuple */ - fill_label_cache_data(entry, tuple, RelationGetDescr(ag_label)); + fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); - return entry; + return &entry->data; } label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph) @@ -1076,6 +1092,7 @@ static label_cache_data *search_label_seq_name_graph_cache_miss(Name name, /* fill the new entry with the retrieved tuple */ fill_label_cache_data(&entry->data, tuple, RelationGetDescr(ag_label)); + cache_label_data_in_all_caches(&entry->data); systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); @@ -1096,6 +1113,45 @@ static void *label_seq_name_graph_cache_hash_search(Name name, Oid graph, return hash_search(label_seq_name_graph_cache_hash, &key, action, found); } +static void cache_label_data_in_all_caches(const label_cache_data *data) +{ + label_name_graph_cache_entry *name_entry; + label_graph_oid_cache_entry *id_entry; + label_relation_cache_entry *relation_entry; + label_seq_name_graph_cache_entry *seq_entry; + bool found; + + name_entry = label_name_graph_cache_hash_search((Name)&data->name, + data->graph, HASH_ENTER, + &found); + if (!found) + { + name_entry->data = *data; + } + + id_entry = label_graph_oid_cache_hash_search(data->graph, data->id, + HASH_ENTER, &found); + if (!found) + { + id_entry->data = *data; + } + + relation_entry = hash_search(label_relation_cache_hash, &data->relation, + HASH_ENTER, &found); + if (!found) + { + relation_entry->data = *data; + } + + seq_entry = label_seq_name_graph_cache_hash_search((Name)&data->seq_name, + data->graph, HASH_ENTER, + &found); + if (!found) + { + seq_entry->data = *data; + } +} + static void fill_label_cache_data(label_cache_data *cache_data, HeapTuple tuple, TupleDesc tuple_desc) { diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index ea8e1bd5b..6ce627c4d 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -134,30 +134,41 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) graph_oid = get_graph_oid(graph_name_str); - if (!PG_ARGISNULL(3)) + graph_cache = search_graph_name_cache(graph_name_str); + vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); + if (vertex_cache == NULL) { - /* Check if label with the input name already exists */ - if (!label_exists(vtx_name_str, graph_oid)) + DirectFunctionCall2(create_vlabel, + CStringGetDatum(graph_name->data), + CStringGetDatum(vtx_name_str)); + vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); + if (vertex_cache == NULL) { - DirectFunctionCall2(create_vlabel, - CStringGetDatum(graph_name->data), - CStringGetDatum(vtx_label_name->data)); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex label \"%s\" was not found after creation", + vtx_name_str))); } } - if (!label_exists(edge_name_str, graph_oid)) + edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + if (edge_cache == NULL) { DirectFunctionCall2(create_elabel, CStringGetDatum(graph_name->data), CStringGetDatum(edge_label_name->data)); + edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + if (edge_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge label \"%s\" was not found after creation", + edge_name_str))); + } } - vtx_label_id = get_label_id(vtx_name_str, graph_oid); - edge_label_id = get_label_id(edge_name_str, graph_oid); - - graph_cache = search_graph_name_cache(graph_name_str); - vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); - edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + vtx_label_id = vertex_cache->id; + edge_label_id = edge_cache->id; nsp_id = graph_cache->namespace; vtx_seq_name = &(vertex_cache->seq_name); @@ -250,6 +261,7 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) graphid end_node_graph_id; graph_cache_data* graph_cache; + label_cache_data* node_cache; label_cache_data* edge_cache; agtype* properties = NULL; @@ -317,15 +329,31 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) arguments->args[3].value); graph_oid = get_graph_oid(graph_name_str); - node_label_id = get_label_id(node_label_str, graph_oid); - edge_label_id = get_label_id(edge_label_str, graph_oid); /* * Fetching caches to get next values for graph id's, and access nodes * to be connected with edges. */ graph_cache = search_graph_name_cache(graph_name_str); - edge_cache = search_label_name_graph_cache(edge_label_str,graph_oid); + node_cache = search_label_name_graph_cache(node_label_str, graph_oid); + if (node_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("vertex label \"%s\" does not exist", + node_label_str))); + } + + edge_cache = search_label_name_graph_cache(edge_label_str, graph_oid); + if (edge_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("edge label \"%s\" does not exist", edge_label_str))); + } + + node_label_id = node_cache->id; + edge_label_id = edge_cache->id; /* connect a node from each graph */ /* first created node, from the first complete graph */ diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index c05bf3352..7b552ebfa 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -29,6 +29,17 @@ #include "utils/rel.h" #include "utils/load/ag_load_edges.h" +#include "utils/ag_cache.h" + +typedef struct vertex_label_id_cache_entry +{ + NameData name; + int32 id; +} vertex_label_id_cache_entry; + +static HTAB *create_vertex_label_id_cache(void); +static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, + Oid graph_oid); /* * Process a single edge row from COPY's raw fields. @@ -38,7 +49,8 @@ static void process_edge_row(char **fields, int nfields, char **header, int header_count, int label_id, Oid label_seq_relid, Oid graph_oid, bool load_as_agtype, - batch_insert_state *batch_state) + batch_insert_state *batch_state, + HTAB *vertex_label_id_cache) { int64 start_id_int; graphid start_vertex_graph_id; @@ -66,11 +78,15 @@ static void process_edge_row(char **fields, int nfields, /* Parse start vertex info */ start_id_int = strtol(fields[0], NULL, 10); - start_vertex_type_id = get_label_id(start_vertex_type, graph_oid); + start_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, + start_vertex_type, + graph_oid); /* Parse end vertex info */ end_id_int = strtol(fields[2], NULL, 10); - end_vertex_type_id = get_label_id(end_vertex_type, graph_oid); + end_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, + end_vertex_type, + graph_oid); /* Create graphids for start and end vertices */ start_vertex_graph_id = make_graphid(start_vertex_type_id, start_id_int); @@ -112,6 +128,38 @@ static void process_edge_row(char **fields, int nfields, } } +static HTAB *create_vertex_label_id_cache(void) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(NameData); + hashctl.entrysize = sizeof(vertex_label_id_cache_entry); + + return hash_create("age edge load vertex label id cache", 16, &hashctl, + HASH_ELEM | HASH_BLOBS); +} + +static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, + Oid graph_oid) +{ + NameData key; + vertex_label_id_cache_entry *entry; + bool found; + + namestrcpy(&key, label_name); + entry = hash_search(cache, &key, HASH_ENTER, &found); + if (!found) + { + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache(label_name, graph_oid); + entry->id = label_cache != NULL ? label_cache->id : -1; + } + + return entry->id; +} + /* * Create COPY options for CSV parsing. * Returns a List of DefElem nodes. @@ -160,6 +208,8 @@ int create_edges_from_csv_file(char *file_path, batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; + label_cache_data *label_cache; + HTAB *vertex_label_id_cache = NULL; /* Create a memory context for batch processing - reset after each batch */ batch_context = AllocSetContextCreate(CurrentMemoryContext, @@ -167,7 +217,8 @@ int create_edges_from_csv_file(char *file_path, ALLOCSET_DEFAULT_SIZES); /* Get the label relation */ - label_relid = get_label_relation(label_name, graph_oid); + label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_relid = label_cache != NULL ? label_cache->relation : InvalidOid; label_rel = table_open(label_relid, RowExclusiveLock); /* Get sequence info */ @@ -175,7 +226,8 @@ int create_edges_from_csv_file(char *file_path, label_seq_relid = get_relname_relid(label_seq_name, graph_oid); /* Initialize the batch insert state */ - init_batch_insert(&batch_state, label_name, graph_oid); + init_batch_insert(&batch_state, label_relid); + vertex_label_id_cache = create_vertex_label_id_cache(); /* Create COPY options for CSV parsing */ copy_options = create_copy_options(); @@ -231,7 +283,7 @@ int create_edges_from_csv_file(char *file_path, header, header_count, label_id, label_seq_relid, graph_oid, load_as_agtype, - batch_state); + batch_state, vertex_label_id_cache); /* Switch back to main context */ MemoryContextSwitchTo(old_context); @@ -247,6 +299,8 @@ int create_edges_from_csv_file(char *file_path, /* Finish any remaining batch inserts */ finish_batch_insert(&batch_state); MemoryContextReset(batch_context); + hash_destroy(vertex_label_id_cache); + vertex_label_id_cache = NULL; /* Clean up COPY state */ EndCopyFrom(cstate); @@ -267,6 +321,11 @@ int create_edges_from_csv_file(char *file_path, /* Close the relation */ table_close(label_rel, RowExclusiveLock); + if (vertex_label_id_cache != NULL) + { + hash_destroy(vertex_label_id_cache); + } + /* Delete batch context */ MemoryContextDelete(batch_context); diff --git a/src/backend/utils/load/ag_load_labels.c b/src/backend/utils/load/ag_load_labels.c index 5b11f68b8..971fcbc54 100644 --- a/src/backend/utils/load/ag_load_labels.c +++ b/src/backend/utils/load/ag_load_labels.c @@ -29,6 +29,7 @@ #include "utils/rel.h" #include "utils/load/ag_load_labels.h" +#include "utils/ag_cache.h" /* * Process a single vertex row from COPY's raw fields. @@ -149,6 +150,7 @@ int create_labels_from_csv_file(char *file_path, batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; + label_cache_data *label_cache; /* Create a memory context for batch processing - reset after each batch */ batch_context = AllocSetContextCreate(CurrentMemoryContext, @@ -156,7 +158,8 @@ int create_labels_from_csv_file(char *file_path, ALLOCSET_DEFAULT_SIZES); /* Get the label relation */ - label_relid = get_label_relation(label_name, graph_oid); + label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_relid = label_cache != NULL ? label_cache->relation : InvalidOid; label_rel = table_open(label_relid, RowExclusiveLock); /* Get sequence info */ @@ -173,7 +176,7 @@ int create_labels_from_csv_file(char *file_path, } /* Initialize the batch insert state */ - init_batch_insert(&batch_state, label_name, graph_oid); + init_batch_insert(&batch_state, label_relid); /* Create COPY options for CSV parsing */ copy_options = create_copy_options(); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index e4f10d7e4..728fa9d4d 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -37,11 +37,13 @@ #include "utils/load/ag_load_edges.h" #include "utils/load/ag_load_labels.h" #include "utils/load/age_load.h" +#include "utils/ag_cache.h" static agtype_value *csv_value_to_agtype_value(char *csv_val); static Oid get_or_create_graph(const Name graph_name); -static int32 get_or_create_label(Oid graph_oid, char *graph_name, - char *label_name, char label_kind); +static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, + char *label_name, + char label_kind); static char *build_safe_filename(char *name); static void check_file_read_permission(void); static void check_table_permissions(Oid relid); @@ -422,16 +424,19 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, bool nulls[4] = {false, false, false, false}; Relation label_relation; HeapTuple tuple; + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache(label_name, graph_oid); /* Check if label provided exists as vertex label, then throw error */ - if (get_label_kind(label_name, graph_oid) == LABEL_KIND_VERTEX) + if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label %s already exists as vertex label", label_name))); } /* Open the relation */ - label_relation = table_open(get_label_relation(label_name, graph_oid), + label_relation = table_open(label_cache != NULL ? label_cache->relation : InvalidOid, RowExclusiveLock); /* Form the tuple */ @@ -471,9 +476,12 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, bool nulls[2] = {false, false}; Relation label_relation; HeapTuple tuple; + label_cache_data *label_cache; + + label_cache = search_label_name_graph_cache(label_name, graph_oid); /* Check if label provided exists as edge label, then throw error */ - if (get_label_kind(label_name, graph_oid) == LABEL_KIND_EDGE) + if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label %s already exists as edge label", @@ -481,7 +489,7 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, } /* Open the relation */ - label_relation = table_open(get_label_relation(label_name, graph_oid), + label_relation = table_open(label_cache != NULL ? label_cache->relation : InvalidOid, RowExclusiveLock); /* Form the tuple */ @@ -545,7 +553,7 @@ void insert_batch(batch_insert_state *batch_state) true, NULL, NIL, false); /* Check if the unique constraint is violated */ - if (list_length(result) != 0) + if (result != NIL) { Datum id; bool isnull; @@ -576,6 +584,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) int32 label_id; bool id_field_exists; bool load_as_agtype; + label_cache_data *label_cache; if (PG_ARGISNULL(0)) { @@ -615,11 +624,12 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); - label_id = get_or_create_label(graph_oid, graph_name_str, - label_name_str, LABEL_KIND_VERTEX); + label_cache = get_or_create_label(graph_oid, graph_name_str, + label_name_str, LABEL_KIND_VERTEX); + label_id = label_cache->id; /* Get the label relation and check permissions */ - label_relid = get_label_relation(label_name_str, graph_oid); + label_relid = label_cache->relation; check_table_permissions(label_relid); check_rls_for_load(label_relid); @@ -645,6 +655,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) Oid label_relid; int32 label_id; bool load_as_agtype; + label_cache_data *label_cache; if (PG_ARGISNULL(0)) { @@ -683,11 +694,12 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) file_path_str = build_safe_filename(text_to_cstring(file_name)); graph_oid = get_or_create_graph(graph_name); - label_id = get_or_create_label(graph_oid, graph_name_str, - label_name_str, LABEL_KIND_EDGE); + label_cache = get_or_create_label(graph_oid, graph_name_str, + label_name_str, LABEL_KIND_EDGE); + label_id = label_cache->id; /* Get the label relation and check permissions */ - label_relid = get_label_relation(label_name_str, graph_oid); + label_relid = label_cache->relation; check_table_permissions(label_relid); check_rls_for_load(label_relid); @@ -727,15 +739,16 @@ static Oid get_or_create_graph(const Name graph_name) * Helper function to create a label if it does not exist. * Just returns label_id of the label if it already exists. */ -static int32 get_or_create_label(Oid graph_oid, char *graph_name, - char *label_name, char label_kind) +static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, + char *label_name, + char label_kind) { - int32 label_id; + label_cache_data *label_cache; - label_id = get_label_id(label_name, graph_oid); + label_cache = search_label_name_graph_cache(label_name, graph_oid); /* Check if label exists */ - if (label_id_is_valid(label_id)) + if (label_cache != NULL) { char *label_kind_full = (label_kind == LABEL_KIND_VERTEX) ? "vertex" : "edge"; @@ -743,7 +756,7 @@ static int32 get_or_create_label(Oid graph_oid, char *graph_name, ? LABEL_KIND_EDGE : LABEL_KIND_VERTEX; /* If it exists, but as another label_kind, throw an error */ - if (get_label_kind(label_name, graph_oid) == opposite_label_kind) + if (label_cache->kind == opposite_label_kind) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -762,23 +775,28 @@ static int32 get_or_create_label(Oid graph_oid, char *graph_name, parent = list_make1(rv); create_label(graph_name, label_name, label_kind, parent); - label_id = get_label_id(label_name, graph_oid); + label_cache = search_label_name_graph_cache(label_name, graph_oid); + if (label_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label \"%s\" was not found after creation", + label_name))); + } ereport(NOTICE, (errmsg("VLabel \"%s\" has been created", label_name))); } - return label_id; + return label_cache; } /* * Initialize the batch insert state. */ -void init_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid) +void init_batch_insert(batch_insert_state **batch_state, Oid relid) { Relation relation; - Oid relid; EState *estate; ResultRelInfo *resultRelInfo; RangeTblEntry *rte; @@ -787,9 +805,6 @@ void init_batch_insert(batch_insert_state **batch_state, List *perminfos = NIL; int i; - /* Get the relation OID */ - relid = get_label_relation(label_name, graph_oid); - /* Initialize executor state */ estate = CreateExecutorState(); diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index 8f6cd03c7..e8223f5fb 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -58,6 +58,7 @@ typedef struct cypher_create_custom_scan_state TupleTableSlot *slot; Oid graph_oid; HTAB *entity_exists_index_cache; + HTAB *result_rel_info_cache; } cypher_create_custom_scan_state; typedef struct cypher_set_custom_scan_state @@ -67,6 +68,7 @@ typedef struct cypher_set_custom_scan_state cypher_update_information *set_list; HTAB *qual_cache; HTAB *index_cache; + HTAB *result_rel_info_cache; Oid graph_oid; int flags; } cypher_set_custom_scan_state; @@ -98,6 +100,7 @@ typedef struct cypher_delete_custom_scan_state HTAB *vertex_id_htab; HTAB *qual_cache; HTAB *index_cache; + HTAB *result_rel_info_cache; } cypher_delete_custom_scan_state; typedef struct cypher_merge_custom_scan_state @@ -121,6 +124,7 @@ typedef struct cypher_merge_custom_scan_state cypher_update_information *on_match_set_info; /* NULL if not specified */ cypher_update_information *on_create_set_info; /* NULL if not specified */ HTAB *entity_exists_index_cache; + HTAB *result_rel_info_cache; } cypher_merge_custom_scan_state; /* Reusable SET logic callable from MERGE executor */ @@ -135,8 +139,14 @@ TupleTableSlot *populate_edge_tts( ResultRelInfo *create_entity_result_rel_info_by_oid(EState *estate, Oid relid); void destroy_entity_result_rel_info(ResultRelInfo *result_rel_info); +HTAB *create_entity_result_rel_info_cache(const char *name); +ResultRelInfo *get_entity_result_rel_info(EState *estate, + HTAB *result_rel_info_cache, + Oid relid); +void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache); HTAB *create_entity_exists_index_cache(const char *name); +void destroy_entity_exists_index_cache(HTAB *index_cache); bool entity_exists(EState *estate, Oid graph_oid, graphid id); bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, HTAB *index_cache); @@ -172,7 +182,24 @@ typedef struct RLSCacheEntry /* Hash table entry for caching index OIDs per label */ typedef struct IndexCacheEntry { Oid relid; /* hash key */ + Relation rel; + bool index_oid_cached; Oid index_oid; + bool start_index_oid_cached; + Oid start_index_oid; + bool end_index_oid_cached; + Oid end_index_oid; } IndexCacheEntry; +static inline void init_index_cache_entry(IndexCacheEntry *entry) +{ + entry->rel = NULL; + entry->index_oid_cached = false; + entry->index_oid = InvalidOid; + entry->start_index_oid_cached = false; + entry->start_index_oid = InvalidOid; + entry->end_index_oid_cached = false; + entry->end_index_oid = InvalidOid; +} + #endif diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 3433bebb0..4abab5459 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -461,6 +461,7 @@ typedef struct cypher_update_information uint32 flags; AttrNumber tuple_position; char *graph_name; + uint32 graph_oid; char *clause_name; } cypher_update_information; diff --git a/src/include/utils/age_global_graph.h b/src/include/utils/age_global_graph.h index 92044fc7e..325527f48 100644 --- a/src/include/utils/age_global_graph.h +++ b/src/include/utils/age_global_graph.h @@ -52,10 +52,12 @@ ListGraphId *get_vertex_entry_edges_in(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_out(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve); Oid get_vertex_entry_label_table_oid(vertex_entry *ve); +char *get_vertex_entry_label_name(vertex_entry *ve); Datum get_vertex_entry_properties(vertex_entry *ve); /* edge entry accessor functions */ graphid get_edge_entry_id(edge_entry *ee); Oid get_edge_entry_label_table_oid(edge_entry *ee); +char *get_edge_entry_label_name(edge_entry *ee); Datum get_edge_entry_properties(edge_entry *ee); graphid get_edge_entry_start_vertex_id(edge_entry *ee); graphid get_edge_entry_end_vertex_id(edge_entry *ee); diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 807d3795e..eaa37288c 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -639,8 +639,6 @@ Datum make_vertex(Datum id, Datum label, Datum properties); Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, Datum properties); Datum make_path(List *path); -Datum column_get_datum(TupleDesc tupdesc, HeapTuple tuple, int column, - const char *attname, Oid typid, bool isnull); agtype_value *agtype_value_build_vertex(graphid id, char *label, Datum properties); agtype_value *agtype_value_build_edge(graphid id, char *label, graphid end_id, diff --git a/src/include/utils/load/age_load.h b/src/include/utils/load/age_load.h index 6573c79f3..2eec6fbf5 100644 --- a/src/include/utils/load/age_load.h +++ b/src/include/utils/load/age_load.h @@ -58,8 +58,7 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, graphid start_id, graphid end_id, agtype *edge_properties); -void init_batch_insert(batch_insert_state **batch_state, - char *label_name, Oid graph_oid); +void init_batch_insert(batch_insert_state **batch_state, Oid relid); void insert_batch(batch_insert_state *batch_state); void finish_batch_insert(batch_insert_state **batch_state); From f28f823358fffa6362a8d453411372f566db9917 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:37 +0900 Subject: [PATCH 04/15] Track graph cache generations Add generation-aware graph and label lookup caches for repeated catalog queries that occur during graph loading, VLE setup, endpoint lookup, graph stats, and label creation. The cache slots remember graph OIDs, namespace lookups, label metadata, and edge constraint state while avoiding invalidation churn for unrelated labels. The accompanying documentation records the focused regression sets used for these graph-cache changes. --- src/backend/catalog/ag_graph.c | 6 +- src/backend/commands/label_commands.c | 51 ++++-- src/backend/executor/cypher_delete.c | 4 +- src/backend/executor/cypher_set.c | 6 +- src/backend/executor/cypher_utils.c | 2 +- src/backend/parser/cypher_clause.c | 95 ++++++---- src/backend/parser/cypher_expr.c | 100 +++++++++-- src/backend/utils/adt/age_global_graph.c | 212 +++++++++++++++++++---- src/backend/utils/adt/age_vle.c | 116 ++++++++++--- src/backend/utils/adt/agtype.c | 65 ++++++- src/backend/utils/cache/ag_cache.c | 210 ++++++++++++++++++++-- src/backend/utils/graph_generation.c | 133 ++++++++++++-- src/backend/utils/load/ag_load_edges.c | 16 +- src/backend/utils/load/ag_load_labels.c | 13 +- src/backend/utils/load/age_load.c | 22 ++- src/include/utils/ag_cache.h | 7 + src/include/utils/age_global_graph.h | 8 + src/include/utils/load/ag_load_edges.h | 5 +- src/include/utils/load/ag_load_labels.h | 5 +- 19 files changed, 870 insertions(+), 206 deletions(-) diff --git a/src/backend/catalog/ag_graph.c b/src/backend/catalog/ag_graph.c index 833cba252..51f2a9256 100644 --- a/src/backend/catalog/ag_graph.c +++ b/src/backend/catalog/ag_graph.c @@ -144,7 +144,7 @@ Oid get_graph_oid(const char *graph_name) { graph_cache_data *cache_data; - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (cache_data) { return cache_data->oid; @@ -159,7 +159,7 @@ static Oid get_graph_namespace(const char *graph_name) { graph_cache_data *cache_data; - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (!cache_data) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), @@ -178,7 +178,7 @@ bool graph_namespace_exists(Oid graph_oid) { graph_cache_data *cache_data; - cache_data = search_graph_namespace_cache(graph_oid); + cache_data = search_graph_namespace_cache_cached(graph_oid); if (cache_data) { return true; diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index ac789ecce..fbf0ed1f5 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -85,6 +85,9 @@ static void create_index_on_column(char *schema_name, char *rel_name, char *colname, bool unique); +static void create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data); PG_FUNCTION_INFO_V1(age_is_valid_label_name); @@ -149,6 +152,7 @@ Datum create_vlabel(PG_FUNCTION_ARGS) { char *graph_name; Oid graph_oid; + graph_cache_data *graph_cache; List *parent; RangeVar *rv; char *label_name; @@ -184,14 +188,15 @@ Datum create_vlabel(PG_FUNCTION_ARGS) } /* Check if graph does not exist */ - if (!graph_exists(graph_name)) + graph_cache = search_graph_name_cache_cached(graph_name); + if (graph_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name))); } - graph_oid = get_graph_oid(graph_name); + graph_oid = graph_cache->oid; /* Check if label with the input name already exists */ if (label_exists(label_name, graph_oid)) @@ -206,7 +211,8 @@ Datum create_vlabel(PG_FUNCTION_ARGS) parent = list_make1(rv); - create_label(graph_name, label_name, LABEL_TYPE_VERTEX, parent); + create_label_with_graph_cache(graph_name, label_name, LABEL_TYPE_VERTEX, + parent, graph_cache); ereport(NOTICE, (errmsg("VLabel \"%s\" has been created", label_name))); @@ -230,6 +236,7 @@ Datum create_elabel(PG_FUNCTION_ARGS) { char *graph_name; Oid graph_oid; + graph_cache_data *graph_cache; List *parent; RangeVar *rv; char *label_name; @@ -265,14 +272,15 @@ Datum create_elabel(PG_FUNCTION_ARGS) } /* Check if graph does not exist */ - if (!graph_exists(graph_name)) + graph_cache = search_graph_name_cache_cached(graph_name); + if (graph_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist.", graph_name))); } - graph_oid = get_graph_oid(graph_name); + graph_oid = graph_cache->oid; /* Check if label with the input name already exists */ if (label_exists(label_name, graph_oid)) @@ -286,7 +294,8 @@ Datum create_elabel(PG_FUNCTION_ARGS) rv = get_label_range_var(graph_name, graph_oid, AG_DEFAULT_LABEL_EDGE); parent = list_make1(rv); - create_label(graph_name, label_name, LABEL_TYPE_EDGE, parent); + create_label_with_graph_cache(graph_name, label_name, LABEL_TYPE_EDGE, + parent, graph_cache); ereport(NOTICE, (errmsg("ELabel \"%s\" has been created", label_name))); @@ -303,14 +312,6 @@ void create_label(char *graph_name, char *label_name, char label_type, List *parents) { graph_cache_data *cache_data; - Oid graph_oid; - Oid nsp_id; - char *schema_name; - char *rel_name; - char *seq_name; - RangeVar *seq_range_var; - int32 label_id; - Oid relation_id; if (!is_valid_label_name(label_name, label_type)) { @@ -318,12 +319,30 @@ void create_label(char *graph_name, char *label_name, char label_type, errmsg("label name is invalid"))); } - cache_data = search_graph_name_cache(graph_name); + cache_data = search_graph_name_cache_cached(graph_name); if (!cache_data) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name))); } + + create_label_with_graph_cache(graph_name, label_name, label_type, parents, + cache_data); +} + +static void create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data) +{ + Oid graph_oid; + Oid nsp_id; + char *schema_name; + char *rel_name; + char *seq_name; + RangeVar *seq_range_var; + int32 label_id; + Oid relation_id; + graph_oid = cache_data->oid; nsp_id = cache_data->namespace; @@ -910,7 +929,7 @@ Datum drop_label(PG_FUNCTION_ARGS) force = PG_GETARG_BOOL(2); graph_name_str = NameStr(*graph_name); - cache_data = search_graph_name_cache(graph_name_str); + cache_data = search_graph_name_cache_cached(graph_name_str); if (!cache_data) { ereport(ERROR, diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index f15b6c31a..b300e03e5 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -463,8 +463,8 @@ static void process_delete_list(CustomScanState *node) entity_position); id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label_cache = search_label_graph_oid_cache(css->delete_data->graph_oid, - GET_LABEL_ID(id->val.int_value)); + label_cache = search_label_graph_oid_cache_cached( + css->delete_data->graph_oid, GET_LABEL_ID(id->val.int_value)); if (label_cache == NULL) { ereport(ERROR, diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 22290be40..b641cdc48 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -25,11 +25,11 @@ #include "utils/rls.h" #include "commands/label_commands.h" +#include "catalog/ag_graph.h" #include "executor/cypher_executor.h" #include "executor/cypher_utils.h" #include "utils/age_global_graph.h" #include "utils/ag_cache.h" -#include "catalog/ag_graph.h" static void begin_cypher_set(CustomScanState *node, EState *estate, int eflags); @@ -68,7 +68,7 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, Plan *subplan; Assert(list_length(css->cs->custom_plans) == 1); - css->graph_oid = get_graph_oid(css->set_list->graph_name); + css->graph_oid = css->set_list->graph_oid; if (!OidIsValid(css->graph_oid)) { ereport(ERROR, @@ -559,7 +559,7 @@ void apply_update_list(CustomScanState *node, /* get the id and label metadata for later */ id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); - label_cache = search_label_graph_oid_cache( + label_cache = search_label_graph_oid_cache_cached( graph_oid, GET_LABEL_ID(id->val.int_value)); if (label_cache == NULL) { diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index 414e7cc42..fc1e2d4ee 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -253,7 +253,7 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, * Extract the label id from the graph id and get the table name * the entity is part of. */ - label = search_label_graph_oid_cache(graph_oid, GET_LABEL_ID(id)); + label = search_label_graph_oid_cache_cached(graph_oid, GET_LABEL_ID(id)); /* Setup the scan key to be the graphid */ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index a22ed2593..0ae3ebf67 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -114,6 +114,11 @@ static Query *transform_cypher_clause_with_where(cypher_parsestate *cpstate, transform_method transform, cypher_clause *clause, Node *where); +static Relation open_label_relation_with_cache(cypher_parsestate *cpstate, + const char *label_name, + int location, + LOCKMODE lockmode, + label_cache_data *label_cache); /* match clause */ static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); @@ -413,11 +418,24 @@ open_label_relation_with_lock(cypher_parsestate *cpstate, const char *label_name, int location, LOCKMODE lockmode) { - label_cache_data *label_cache; + return open_label_relation_with_cache(cpstate, label_name, location, + lockmode, NULL); +} + +static Relation +open_label_relation_with_cache(cypher_parsestate *cpstate, + const char *label_name, int location, + LOCKMODE lockmode, + label_cache_data *label_cache) +{ Oid relid; - label_cache = search_label_name_graph_cache(label_name, - cpstate->graph_oid); + if (label_cache == NULL) + { + label_cache = search_label_name_graph_cache_cached(label_name, + cpstate->graph_oid); + } + relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (!OidIsValid(relid)) { @@ -430,14 +448,6 @@ open_label_relation_with_lock(cypher_parsestate *cpstate, return table_open(relid, lockmode); } -static Relation -open_label_relation(cypher_parsestate *cpstate, const char *label_name, - int location) -{ - return open_label_relation_with_lock(cpstate, label_name, location, - RowExclusiveLock); -} - /* * Add required permissions to the label table for a given entity variable. * Looks up the entity by variable name, extracts its label, and adds @@ -473,7 +483,8 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, return; } - label_cache = search_label_name_graph_cache(label, cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached(label, + cpstate->graph_oid); relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (OidIsValid(relid)) { @@ -2911,8 +2922,8 @@ static bool match_check_valid_label(cypher_match *match, if (node->label) { label_cache_data *lcd = - search_label_name_graph_cache(node->label, - cpstate->graph_oid); + search_label_name_graph_cache_cached( + node->label, cpstate->graph_oid); if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) @@ -2930,8 +2941,8 @@ static bool match_check_valid_label(cypher_match *match, if (rel->label) { label_cache_data *lcd = - search_label_name_graph_cache(rel->label, - cpstate->graph_oid); + search_label_name_graph_cache_cached( + rel->label, cpstate->graph_oid); if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) { @@ -4398,8 +4409,8 @@ static List *make_edge_quals(cypher_parsestate *cpstate, static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label) { - label_cache_data *lcd = search_label_name_graph_cache(label, - cpstate->graph_oid); + label_cache_data *lcd = search_label_name_graph_cache_cached( + label, cpstate->graph_oid); A_Const *n; FuncCall *fc; String *ag_catalog, *extract_label_id; @@ -4979,8 +4990,8 @@ static bool path_check_valid_label(cypher_path *path, if (node->label) { label_cache_data *lcd = - search_label_name_graph_cache(node->label, - cpstate->graph_oid); + search_label_name_graph_cache_cached(node->label, + cpstate->graph_oid); if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) { @@ -4997,8 +5008,8 @@ static bool path_check_valid_label(cypher_path *path, if (rel->label) { label_cache_data *lcd = - search_label_name_graph_cache(rel->label, - cpstate->graph_oid); + search_label_name_graph_cache_cached(rel->label, + cpstate->graph_oid); if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) { @@ -6524,8 +6535,8 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, parser_errposition(&cpstate->pstate, edge->location))); } - label_cache = search_label_name_graph_cache(edge->label, - cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached(edge->label, + cpstate->graph_oid); if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, @@ -6549,7 +6560,10 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, } /* lock the relation of the label */ - label_relation = open_label_relation(cpstate, edge->label, edge->location); + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -6614,8 +6628,8 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, if (node->label) { - label_cache = search_label_name_graph_cache(node->label, - cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached( + node->label, cpstate->graph_oid); if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -6814,8 +6828,8 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->label_name = node->label; } - label_cache = search_label_name_graph_cache(node->label, - cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached(node->label, + cpstate->graph_oid); /* create the label entry if it does not exist */ if (label_cache == NULL) @@ -6833,7 +6847,10 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - label_relation = open_label_relation(cpstate, node->label, node->location); + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8078,8 +8095,8 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } - label_cache = search_label_name_graph_cache(edge->label, - cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached(edge->label, + cpstate->graph_oid); if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8106,7 +8123,10 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } /* lock the relation of the label */ - label_relation = open_label_relation(cpstate, edge->label, edge->location); + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8206,8 +8226,8 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->label_name = node->label; } - label_cache = search_label_name_graph_cache(node->label, - cpstate->graph_oid); + label_cache = search_label_name_graph_cache_cached(node->label, + cpstate->graph_oid); if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8236,7 +8256,10 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; - label_relation = open_label_relation(cpstate, node->label, node->location); + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); /* Store the relid */ rel->relid = RelationGetRelid(label_relation); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 8ba66b43f..92a9bda2a 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -40,7 +40,9 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/float.h" +#include "utils/hsearch.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "parser/cypher_expr.h" #include "parser/cypher_transform_entity.h" @@ -59,6 +61,20 @@ #define FUNC_AGTYPE_TYPECAST_BOOL "agtype_typecast_bool" #define FUNC_AGTYPE_TYPECAST_PG_TEXT "agtype_to_text" +typedef struct function_exists_cache_key +{ + NameData funcname; + NameData extension; +} function_exists_cache_key; + +typedef struct function_exists_cache_entry +{ + function_exists_cache_key key; + bool exists; +} function_exists_cache_entry; + +static HTAB *function_exists_cache = NULL; + static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); static Node *transform_A_Const(cypher_parsestate *cpstate, A_Const *ac); @@ -112,6 +128,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); static char *get_mapped_extension(Oid func_oid); static bool is_extension_external(char *extension); static char *construct_age_function_name(char *funcname); +static void initialize_function_exists_cache(void); static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, Oid source_oid, Oid target_oid, @@ -1960,14 +1977,40 @@ static bool function_exists(char *funcname, char *extension) { CatCList *catlist = NULL; bool found = false; + function_exists_cache_key key; + function_exists_cache_entry *entry; + bool cache_found; int i = 0; + if (extension != NULL) + { + initialize_function_exists_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.funcname, funcname); + namestrcpy(&key.extension, extension); + + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + if (cache_found) + { + return entry->exists; + } + } + else + { + entry = NULL; + } + /* get a list of matching functions */ catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); if (catlist->n_members == 0) { ReleaseSysCacheList(catlist); + if (entry != NULL) + { + entry->exists = false; + } return false; } else if (extension == NULL) @@ -1992,9 +2035,38 @@ static bool function_exists(char *funcname, char *extension) /* we need to release the cache list */ ReleaseSysCacheList(catlist); + if (entry != NULL) + { + entry->exists = found; + } + return found; } +static void initialize_function_exists_cache(void) +{ + HASHCTL hash_ctl; + + if (function_exists_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(function_exists_cache_key); + hash_ctl.entrysize = sizeof(function_exists_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_exists_cache = hash_create("cypher function existence cache", 64, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + /* * Code borrowed from PG's transformFuncCall and updated for AGE */ @@ -2062,10 +2134,22 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * If it's not in age, check if it's a potential call to some function * in another installed extension. */ - else if(function_exists(name, NULL)) + else { - Form_pg_proc procform = get_procform(fn, true); - char *extension = get_mapped_extension(procform->oid); + Form_pg_proc procform = get_procform(fn, false); + char *extension; + + if (procform == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", name), + errhint("If the function is from an external extension, " + "make sure the extension is installed and the " + "function is in the search path."))); + } + + extension = get_mapped_extension(procform->oid); /* * If the function is from another extension, transform @@ -2086,16 +2170,6 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) fname = fn->funcname; } } - /* no function found */ - else - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", name), - errhint("If the function is from an external extension, " - "make sure the extension is installed and the " - "function is in the search path."))); - } } /* ... and hand off to ParseFuncOrColumn */ diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 6302f81ec..0acdcc3d6 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -80,6 +80,12 @@ typedef struct GraphVersionState GraphVersionEntry entries[AGE_MAX_GRAPHS]; } GraphVersionState; +typedef struct EntryPropertyRelationCacheEntry +{ + Oid relid; + Relation rel; +} EntryPropertyRelationCacheEntry; + /* * Version mode detection — determined once per backend on first use. * DSM: PG 17+ GetNamedDSMSegment (no shared_preload_libraries needed) @@ -95,6 +101,8 @@ typedef enum } VersionMode; static VersionMode version_mode = VERSION_MODE_UNKNOWN; +static Oid cached_version_graph_oid = InvalidOid; +static int cached_version_graph_index = -1; /* For PG < 17 shmem path */ static GraphVersionState *shmem_version_state = NULL; @@ -167,8 +175,10 @@ static GRAPH_global_context_container global_graph_contexts_container = {0}; /* declarations */ /* GRAPH global context functions */ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx); +static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid); static bool delete_specific_GRAPH_global_contexts(char *graph_name); static bool delete_GRAPH_global_contexts(void); +static Oid get_cached_global_graph_oid(const char *graph_name); static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_vertex_hashtable(GRAPH_global_context *ggctx); @@ -187,6 +197,11 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, char *vertex_label_name, ItemPointerData tid); +static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, + AttrNumber property_attr, + HTAB *relation_cache, + const char *stale_msg); +static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache); /* definitions */ /* @@ -1082,8 +1097,6 @@ static bool delete_GRAPH_global_contexts(void) */ static bool delete_specific_GRAPH_global_contexts(char *graph_name) { - GRAPH_global_context *prev_ggctx = NULL; - GRAPH_global_context *curr_ggctx = NULL; Oid graph_oid = InvalidOid; if (graph_name == NULL) @@ -1092,7 +1105,44 @@ static bool delete_specific_GRAPH_global_contexts(char *graph_name) } /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid(graph_name); + + return delete_specific_GRAPH_global_context_by_oid(graph_oid); +} + +static Oid get_cached_global_graph_oid(const char *graph_name) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + + if (OidIsValid(cached_graph_oid) && + namestrcmp(&cached_graph_name, graph_name) == 0 && + cached_generation == current_generation) + { + return cached_graph_oid; + } + + cached_graph_oid = get_graph_oid(graph_name); + if (OidIsValid(cached_graph_oid)) + { + namestrcpy(&cached_graph_name, graph_name); + cached_generation = get_graph_cache_generation(); + } + + return cached_graph_oid; +} + +static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid) +{ + GRAPH_global_context *prev_ggctx = NULL; + GRAPH_global_context *curr_ggctx = NULL; + + if (!OidIsValid(graph_oid)) + { + return false; + } /* lock the global contexts list */ pthread_mutex_lock(&global_graph_contexts_container.mutex_lock); @@ -1224,6 +1274,11 @@ ListGraphId *get_graph_vertices(GRAPH_global_context *ggctx) return ggctx->vertices; } +int64 get_graph_num_loaded_edges(GRAPH_global_context *ggctx) +{ + return ggctx->num_loaded_edges; +} + /* vertex_entry accessor functions */ graphid get_vertex_entry_id(vertex_entry *ve) { @@ -1270,14 +1325,33 @@ char *get_vertex_entry_label_name(vertex_entry *ve) * the invalidation logic. */ Datum get_vertex_entry_properties(vertex_entry *ve) +{ + return get_vertex_entry_properties_with_cache(ve, NULL); +} + +Datum get_vertex_entry_properties_with_cache(vertex_entry *ve, + HTAB *relation_cache) +{ + return fetch_entry_properties( + ve->vertex_label_table_oid, ve->tid, 2, relation_cache, + "get_vertex_entry_properties: stale TID - " + "vertex entry references a tuple that is no longer visible"); +} + +static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, + AttrNumber property_attr, + HTAB *relation_cache, + const char *stale_msg) { Relation rel; HeapTupleData tuple; Buffer buffer; Datum result = (Datum) 0; + bool close_rel = false; - rel = table_open(ve->vertex_label_table_oid, AccessShareLock); - tuple.t_self = ve->tid; + rel = get_entry_property_relation(relid, relation_cache); + close_rel = relation_cache == NULL; + tuple.t_self = tid; if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true)) { @@ -1285,8 +1359,7 @@ Datum get_vertex_entry_properties(vertex_entry *ve) bool isnull; Datum props; - /* properties is column 2 (1-indexed) */ - props = heap_getattr(&tuple, 2, tupdesc, &isnull); + props = heap_getattr(&tuple, property_attr, tupdesc, &isnull); if (!isnull) { result = datumCopy(props, false, -1); @@ -1295,7 +1368,10 @@ Datum get_vertex_entry_properties(vertex_entry *ve) ReleaseBuffer(buffer); } - table_close(rel, AccessShareLock); + if (close_rel) + { + table_close(rel, AccessShareLock); + } /* * If heap_fetch failed, the tuple is no longer visible. This should @@ -1304,8 +1380,7 @@ Datum get_vertex_entry_properties(vertex_entry *ve) */ if (result == (Datum) 0) { - elog(ERROR, "get_vertex_entry_properties: stale TID - " - "vertex entry references a tuple that is no longer visible"); + elog(ERROR, "%s", stale_msg); } return result; @@ -1333,39 +1408,71 @@ char *get_edge_entry_label_name(edge_entry *ee) */ Datum get_edge_entry_properties(edge_entry *ee) { - Relation rel; - HeapTupleData tuple; - Buffer buffer; - Datum result = (Datum) 0; + return get_edge_entry_properties_with_cache(ee, NULL); +} - rel = table_open(ee->edge_label_table_oid, AccessShareLock); - tuple.t_self = ee->tid; +Datum get_edge_entry_properties_with_cache(edge_entry *ee, + HTAB *relation_cache) +{ + return fetch_entry_properties( + ee->edge_label_table_oid, ee->tid, 4, relation_cache, + "get_edge_entry_properties: stale TID - " + "edge entry references a tuple that is no longer visible"); +} - if (heap_fetch(rel, GetActiveSnapshot(), &tuple, &buffer, true)) +HTAB *create_entry_property_relation_cache(const char *name) +{ + HASHCTL hash_ctl; + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(EntryPropertyRelationCacheEntry); + hash_ctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +void destroy_entry_property_relation_cache(HTAB *relation_cache) +{ + HASH_SEQ_STATUS hash_seq; + EntryPropertyRelationCacheEntry *entry; + + if (relation_cache == NULL) { - TupleDesc tupdesc = RelationGetDescr(rel); - bool isnull; - Datum props; + return; + } - /* properties is column 4 (1-indexed) */ - props = heap_getattr(&tuple, 4, tupdesc, &isnull); - if (!isnull) + hash_seq_init(&hash_seq, relation_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->rel != NULL) { - result = datumCopy(props, false, -1); + table_close(entry->rel, AccessShareLock); } - - ReleaseBuffer(buffer); } - table_close(rel, AccessShareLock); + hash_destroy(relation_cache); +} - if (result == (Datum) 0) +static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache) +{ + EntryPropertyRelationCacheEntry *entry; + bool found; + + if (relation_cache == NULL) { - elog(ERROR, "get_edge_entry_properties: stale TID - " - "edge entry references a tuple that is no longer visible"); + return table_open(relid, AccessShareLock); } - return result; + entry = hash_search(relation_cache, &relid, HASH_ENTER, &found); + if (!found) + { + entry->relid = relid; + entry->rel = table_open(relid, AccessShareLock); + } + + return entry->rel; } graphid get_edge_entry_start_vertex_id(edge_entry *ee) @@ -1465,7 +1572,7 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) agtv_temp->val.string.len); /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid(graph_name); /* * Create or retrieve the GRAPH global context for this graph. This function @@ -1564,14 +1671,14 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) graph_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); + /* get the graph oid */ + graph_oid = get_cached_global_graph_oid(graph_name); + /* * Remove any context for this graph. This is done to allow graph_stats to * show any load issues. */ - delete_specific_GRAPH_global_contexts(graph_name); - - /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + delete_specific_GRAPH_global_context_by_oid(graph_oid); /* * Create or retrieve the GRAPH global context for this graph. This function @@ -1764,15 +1871,30 @@ uint64 get_graph_version(Oid graph_oid) return 0; } + if (cached_version_graph_oid == graph_oid && + cached_version_graph_index >= 0 && + cached_version_graph_index < state->num_entries && + state->entries[cached_version_graph_index].graph_oid == graph_oid) + { + return pg_atomic_read_u64( + &state->entries[cached_version_graph_index].version); + } + /* lock-free scan of the array */ for (i = 0; i < state->num_entries; i++) { if (state->entries[i].graph_oid == graph_oid) { + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + return pg_atomic_read_u64(&state->entries[i].version); } } + cached_version_graph_oid = InvalidOid; + cached_version_graph_index = -1; + return 0; } @@ -1791,11 +1913,24 @@ void increment_graph_version(Oid graph_oid) return; } + if (cached_version_graph_oid == graph_oid && + cached_version_graph_index >= 0 && + cached_version_graph_index < state->num_entries && + state->entries[cached_version_graph_index].graph_oid == graph_oid) + { + pg_atomic_fetch_add_u64( + &state->entries[cached_version_graph_index].version, 1); + return; + } + /* try to find existing entry (lock-free) */ for (i = 0; i < state->num_entries; i++) { if (state->entries[i].graph_oid == graph_oid) { + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + pg_atomic_fetch_add_u64(&state->entries[i].version, 1); return; } @@ -1810,6 +1945,9 @@ void increment_graph_version(Oid graph_oid) if (state->entries[i].graph_oid == graph_oid) { LWLockRelease(&state->lock); + cached_version_graph_oid = graph_oid; + cached_version_graph_index = i; + pg_atomic_fetch_add_u64(&state->entries[i].version, 1); return; } @@ -1822,6 +1960,8 @@ void increment_graph_version(Oid graph_oid) state->entries[idx].graph_oid = graph_oid; pg_atomic_init_u64(&state->entries[idx].version, 1); + cached_version_graph_oid = graph_oid; + cached_version_graph_index = idx; /* * Write barrier ensures the entry fields are fully visible to diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 6bd29d1c8..f9885df0d 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -65,6 +65,7 @@ #include "common/hashfn.h" #include "funcapi.h" +#include "utils/builtins.h" #include "utils/datum.h" #include "utils/lsyscache.h" @@ -78,7 +79,7 @@ #define GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc) \ (graphid *) (&vpc->graphid_array_data) #define EDGE_STATE_HTAB_NAME "Edge state " -#define EDGE_STATE_HTAB_INITIAL_SIZE 100000 +#define EDGE_STATE_HTAB_MIN_SIZE 16 #define EXISTS_HTAB_NAME "known edges" #define EXISTS_HTAB_NAME_INITIAL_SIZE 1000 #define MAXIMUM_NUMBER_OF_CACHED_LOCAL_CONTEXTS 5 @@ -117,6 +118,7 @@ typedef struct VLE_local_context char *edge_label_name; /* edge label name for match */ Oid edge_label_name_oid; /* edge label name oid for match */ agtype *edge_property_constraint; /* edge property constraint as agtype */ + int num_edge_property_constraints; /* number of edge property constraints */ Datum edge_property_constraint_datum; /* edge property constraint as Datum */ uint32 edge_property_constraint_hash; /* edge property constraint hash */ int64 lidx; /* lower (start) bound index */ @@ -157,10 +159,12 @@ typedef struct VLE_path_container static VLE_local_context *global_vle_local_contexts = NULL; /* agtype functions */ -static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee); +static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, + HTAB *relation_cache); /* VLE local context functions */ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); +static Oid get_cached_vle_graph_oid(const char *graph_name); static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx); static void free_VLE_local_context(VLE_local_context *vlelctx); /* VLE graph traversal functions */ @@ -174,7 +178,8 @@ static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx); static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id); static graphid get_next_vertex(VLE_local_context *vlelctx, edge_entry *ee); -static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id); +static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, + int64 stack_size); /* VLE path and edge building functions */ static VLE_path_container *create_VLE_path_container(int64 path_size); static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx); @@ -340,6 +345,7 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) HASHCTL edge_state_ctl; char *graph_name = NULL; char *eshn = NULL; + long initial_size; int glen; int elen; @@ -360,8 +366,10 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) edge_state_ctl.keysize = sizeof(int64); edge_state_ctl.entrysize = sizeof(edge_state_entry); edge_state_ctl.hash = tag_hash; - vlelctx->edge_state_hashtable = hash_create(eshn, - EDGE_STATE_HTAB_INITIAL_SIZE, + + initial_size = Max(get_graph_num_loaded_edges(vlelctx->ggctx), + EDGE_STATE_HTAB_MIN_SIZE); + vlelctx->edge_state_hashtable = hash_create(eshn, initial_size, &edge_state_ctl, HASH_ELEM | HASH_FUNCTION); pfree_if_not_null(eshn); @@ -371,7 +379,8 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) * Helper function to compare the edge constraint (properties we are looking * for in a matching edge) against an edge entry's property. */ -static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) +static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, + HTAB *relation_cache) { agtype *edge_property = NULL; agtype_container *agtc_edge_property = NULL; @@ -383,7 +392,7 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) int num_edge_properties = 0; /* get the number of conditions from the prototype edge */ - num_edge_property_constraints = AGT_ROOT_COUNT(vlelctx->edge_property_constraint); + num_edge_property_constraints = vlelctx->num_edge_property_constraints; /* * We only care about verifying that we have all of the property conditions. @@ -426,7 +435,8 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee) * it multiple times for the same edge. */ { - Datum edge_props_datum = get_edge_entry_properties(ee); + Datum edge_props_datum = get_edge_entry_properties_with_cache( + ee, relation_cache); edge_property = DATUM_GET_AGTYPE_P(edge_props_datum); agtc_edge_property_constraint = &vlelctx->edge_property_constraint->root; @@ -707,7 +717,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, graph_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); /* get the graph oid */ - graph_oid = get_graph_oid(graph_name); + graph_oid = get_cached_vle_graph_oid(graph_name); /* * Create or retrieve the GRAPH global context for this graph. This function @@ -818,6 +828,8 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, /* store the properties as an agtype */ vlelctx->edge_property_constraint = agt_edge_property_constraint; + vlelctx->num_edge_property_constraints = + AGT_ROOT_COUNT(agt_edge_property_constraint); d_edge_property_constraint = AGTYPE_P_GET_DATUM(agt_edge_property_constraint); vlelctx->edge_property_constraint_datum = d_edge_property_constraint; @@ -833,8 +845,8 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, vlelctx->edge_label_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); - label_cache = search_label_name_graph_cache(vlelctx->edge_label_name, - graph_oid); + label_cache = search_label_name_graph_cache_cached( + vlelctx->edge_label_name, graph_oid); vlelctx->edge_label_name_oid = label_cache != NULL ? label_cache->relation : InvalidOid; } @@ -904,6 +916,30 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, return vlelctx; } +static Oid get_cached_vle_graph_oid(const char *graph_name) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + + if (OidIsValid(cached_graph_oid) && + namestrcmp(&cached_graph_name, graph_name) == 0 && + cached_generation == current_generation) + { + return cached_graph_oid; + } + + cached_graph_oid = get_graph_oid(graph_name); + if (OidIsValid(cached_graph_oid)) + { + namestrcpy(&cached_graph_name, graph_name); + cached_generation = get_graph_cache_generation(); + } + + return cached_graph_oid; +} + /* * Helper function to get the specified edge's state. If it does not find it, it * creates and initializes it. @@ -1020,6 +1056,7 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) edge_state_entry *ese = NULL; edge_entry *ee = NULL; bool found = false; + int64 path_length; /* get an edge, but leave it on the stack for now */ edge_id = gid_stack_peek(edge_stack); @@ -1070,6 +1107,7 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) */ ese->used_in_path = true; gid_stack_push(path_stack, edge_id); + path_length = gid_stack_size(path_stack); /* now get the edge entry so we can get the next vertex to move to */ ee = get_edge_entry(vlelctx->ggctx, edge_id); @@ -1080,9 +1118,9 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) * within the bounds specified? */ if (next_vertex_id == end_vertex_id && - gid_stack_size(path_stack) >= vlelctx->lidx && + path_length >= vlelctx->lidx && (vlelctx->uidx_infinite || - gid_stack_size(path_stack) <= vlelctx->uidx)) + path_length <= vlelctx->uidx)) { /* we found one */ found = true; @@ -1094,14 +1132,14 @@ static bool dfs_find_a_path_between(VLE_local_context *vlelctx) */ if (next_vertex_id == end_vertex_id && !vlelctx->uidx_infinite && - gid_stack_size(path_stack) > vlelctx->uidx) + path_length > vlelctx->uidx) { continue; } /* add in the edges for the next vertex if we won't exceed the bounds */ if (vlelctx->uidx_infinite || - gid_stack_size(path_stack) < vlelctx->uidx) + path_length < vlelctx->uidx) { add_valid_vertex_edges(vlelctx, next_vertex_id); } @@ -1148,6 +1186,7 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) edge_state_entry *ese = NULL; edge_entry *ee = NULL; bool found = false; + int64 path_length; /* get an edge, but leave it on the stack for now */ edge_id = gid_stack_peek(edge_stack); @@ -1198,6 +1237,7 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) */ ese->used_in_path = true; gid_stack_push(path_stack, edge_id); + path_length = gid_stack_size(path_stack); /* now get the edge entry so we can get the next vertex to move to */ ee = get_edge_entry(vlelctx->ggctx, edge_id); @@ -1207,9 +1247,9 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) * Is this a path that meets our requirements? Is its length within the * bounds specified? */ - if (gid_stack_size(path_stack) >= vlelctx->lidx && + if (path_length >= vlelctx->lidx && (vlelctx->uidx_infinite || - gid_stack_size(path_stack) <= vlelctx->uidx)) + path_length <= vlelctx->uidx)) { /* we found one */ found = true; @@ -1217,7 +1257,7 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) /* add in the edges for the next vertex if we won't exceed the bounds */ if (vlelctx->uidx_infinite || - gid_stack_size(path_stack) < vlelctx->uidx) + path_length < vlelctx->uidx) { add_valid_vertex_edges(vlelctx, next_vertex_id); } @@ -1237,13 +1277,14 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) * smaller sized lists. But, it is O(n) so it should only be used for small * path_stacks and where appropriate. */ -static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id) +static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, + int64 stack_size) { GraphIdStack *stack = vlelctx->dfs_path_stack; int64 i; /* scan the array-based path stack */ - for (i = 0; i < gid_stack_size(stack); i++) + for (i = 0; i < stack_size; i++) { if (gid_stack_get(stack, i) == edge_id) { @@ -1274,6 +1315,7 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, GraphIdNode *edge_in = NULL; GraphIdNode *edge_out = NULL; GraphIdNode *edge_self = NULL; + HTAB *relation_cache = NULL; /* get the vertex entry */ ve = get_vertex_entry(vlelctx->ggctx, vertex_id); @@ -1304,12 +1346,19 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, edges = get_vertex_entry_edges_self(ve); edge_self = (edges != NULL) ? get_list_head(edges) : NULL; + if (vlelctx->num_edge_property_constraints > 0) + { + relation_cache = create_entry_property_relation_cache( + "VLE edge match relation cache"); + } + /* add in valid vertex edges */ while (edge_out != NULL || edge_in != NULL || edge_self != NULL) { edge_entry *ee = NULL; edge_state_entry *ese = NULL; graphid edge_id; + int64 path_stack_size; /* get the edge_id from the next available edge*/ if (edge_out != NULL) @@ -1329,8 +1378,9 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, * This is a fast existence check, relative to the hash search, for when * the path stack is small. If the edge is in the path, we skip it. */ - if (gid_stack_size(vlelctx->dfs_path_stack) < 10 && - is_edge_in_path(vlelctx, edge_id)) + path_stack_size = gid_stack_size(vlelctx->dfs_path_stack); + if (path_stack_size < 10 && + is_edge_in_path(vlelctx, edge_id, path_stack_size)) { /* set to the next available edge */ if (edge_out != NULL) @@ -1364,7 +1414,8 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, if (!ese->used_in_path) { /* validate the edge if it hasn't been already */ - if (!ese->has_been_matched && is_an_edge_match(vlelctx, ee)) + if (!ese->has_been_matched && + is_an_edge_match(vlelctx, ee, relation_cache)) { ese->has_been_matched = true; ese->matched = true; @@ -1406,6 +1457,8 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, edge_self = next_GraphIdNode(edge_self); } } + + destroy_entry_property_relation_cache(relation_cache); } /* @@ -1587,6 +1640,7 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state edges_result; + HTAB *relation_cache; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; @@ -1608,6 +1662,8 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) MemSet(&edges_result, 0, sizeof(agtype_in_state)); edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + relation_cache = create_entry_property_relation_cache( + "vle edge materialization relation cache"); for (index = 1; index < graphid_array_size - 1; index += 2) { @@ -1622,7 +1678,8 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, get_edge_entry_end_vertex_id(ee), get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties(ee)); + get_edge_entry_properties_with_cache( + ee, relation_cache)); /* push the edge*/ edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_ELEM, agtv_edge); @@ -1631,6 +1688,7 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) /* close our agtype array */ edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); /* make it an array */ edges_result.res->type = AGTV_ARRAY; @@ -1650,6 +1708,7 @@ static agtype_value *build_path(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state path_result; + HTAB *relation_cache; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; @@ -1671,6 +1730,8 @@ static agtype_value *build_path(VLE_path_container *vpc) MemSet(&path_result, 0, sizeof(agtype_in_state)); path_result.res = push_agtype_value(&path_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + relation_cache = create_entry_property_relation_cache( + "vle path materialization relation cache"); for (index = 0; index < graphid_array_size; index += 2) { @@ -1686,7 +1747,8 @@ static agtype_value *build_path(VLE_path_container *vpc) /* reconstruct the vertex */ agtv_vertex = agtype_value_build_vertex(get_vertex_entry_id(ve), label_name, - get_vertex_entry_properties(ve)); + get_vertex_entry_properties_with_cache( + ve, relation_cache)); /* push the vertex */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_vertex); @@ -1707,7 +1769,8 @@ static agtype_value *build_path(VLE_path_container *vpc) agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, get_edge_entry_end_vertex_id(ee), get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties(ee)); + get_edge_entry_properties_with_cache( + ee, relation_cache)); /* push the edge*/ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_edge); @@ -1716,6 +1779,7 @@ static agtype_value *build_path(VLE_path_container *vpc) /* close our agtype array */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); /* make it a path */ path_result.res->type = AGTV_PATH; diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index f98782de3..6f94f8c96 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -161,8 +161,10 @@ static bool is_object_vertex(agtype_value *agtv); static bool is_object_edge(agtype_value *agtv); static bool is_array_path(agtype_value *agtv); /* graph entity retrieval */ +static Oid get_cached_graph_oid_for_name(const char *graph_name); static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid); +static Oid get_cached_vertex_id_index_oid(Relation vertex_label_relation); static char *get_label_name(const char *graph_name, graphid element_graphid, Oid *label_relation); static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, @@ -5629,17 +5631,18 @@ Datum age_end_id(PG_FUNCTION_ARGS) /* * Function to retrieve a label name, given the graph name and graphid of the - * node or edge. The function returns a pointer to a duplicated string that - * needs to be freed when you are finished using it. + * node or edge. The returned pointer is owned by the label cache and must not + * be modified or freed. */ static char *get_label_name(const char *graph_name, graphid element_graphid, Oid *label_relation) { - Oid graph_oid = get_graph_oid(graph_name); + Oid graph_oid = get_cached_graph_oid_for_name(graph_name); int32 label_id = get_graphid_label_id(element_graphid); label_cache_data *label_cache; - label_cache = search_label_graph_oid_cache(graph_oid, label_id); + label_cache = search_label_graph_oid_cache_cached(graph_oid, label_id); + if (label_cache == NULL) { ereport(ERROR, @@ -5649,7 +5652,31 @@ static char *get_label_name(const char *graph_name, graphid element_graphid, *label_relation = label_cache->relation; - return pstrdup(NameStr(label_cache->name)); + return NameStr(label_cache->name); +} + +static Oid get_cached_graph_oid_for_name(const char *graph_name) +{ + static NameData cached_graph_name; + static Oid cached_graph_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_graph_cache_generation(); + + if (OidIsValid(cached_graph_oid) && + namestrcmp(&cached_graph_name, graph_name) == 0 && + cached_generation == current_generation) + { + return cached_graph_oid; + } + + cached_graph_oid = get_graph_oid(graph_name); + if (OidIsValid(cached_graph_oid)) + { + namestrcpy(&cached_graph_name, graph_name); + cached_generation = get_graph_cache_generation(); + } + + return cached_graph_oid; } static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, @@ -5681,8 +5708,7 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, /* open the relation (table) */ graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); - index_oid = find_usable_btree_index_for_attr(graph_vertex_label, - Anum_ag_label_vertex_table_id); + index_oid = get_cached_vertex_id_index_oid(graph_vertex_label); if (OidIsValid(index_oid)) { @@ -12165,6 +12191,31 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, return result; } +static Oid get_cached_vertex_id_index_oid(Relation vertex_label_relation) +{ + static Oid cached_relid = InvalidOid; + static Oid cached_index_oid = InvalidOid; + static uint64 cached_generation = 0; + static bool cached = false; + Oid relid = RelationGetRelid(vertex_label_relation); + uint64 current_generation = get_label_cache_generation(); + + if (cached && + cached_relid == relid && + cached_generation == current_generation) + { + return cached_index_oid; + } + + cached_index_oid = find_usable_btree_index_for_attr( + vertex_label_relation, Anum_ag_label_vertex_table_id); + cached_relid = relid; + cached_generation = get_label_cache_generation(); + cached = true; + + return cached_index_oid; +} + /* * Helper function to find a valid index for a specific attribute. * Returns the OID of the index, or InvalidOid if none is found. diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index e713b4744..eb0b8f510 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -30,6 +30,8 @@ #include "catalog/ag_label.h" #include "utils/ag_cache.h" +#define LAST_LABEL_CACHE_SIZE 4 + typedef struct graph_name_cache_entry { NameData name; /* hash key */ @@ -87,6 +89,7 @@ typedef struct label_seq_name_graph_cache_entry /* ag_graph.name */ static HTAB *graph_name_cache_hash = NULL; static ScanKeyData graph_name_scan_keys[1]; +static uint64 graph_cache_generation = 1; /* ag_graph.namespace */ static HTAB *graph_namespace_cache_hash = NULL; @@ -95,6 +98,7 @@ static ScanKeyData graph_namespace_scan_keys[1]; /* ag_label.name, ag_label.graph */ static HTAB *label_name_graph_cache_hash = NULL; static ScanKeyData label_name_graph_scan_keys[2]; +static uint64 label_cache_generation = 1; /* ag_label.graph, ag_label.id */ static HTAB *label_graph_oid_cache_hash = NULL; @@ -139,13 +143,13 @@ static void create_label_graph_oid_cache(void); static void create_label_relation_cache(void); static void create_label_seq_name_graph_cache(void); static void invalidate_label_caches(Datum arg, Oid relid); -static void invalidate_label_name_graph_cache(Oid relid); +static bool invalidate_label_name_graph_cache(Oid relid); static void flush_label_name_graph_cache(void); -static void invalidate_label_graph_oid_cache(Oid relid); +static bool invalidate_label_graph_oid_cache(Oid relid); static void flush_label_graph_oid_cache(void); -static void invalidate_label_relation_cache(Oid relid); +static bool invalidate_label_relation_cache(Oid relid); static void flush_label_relation_cache(void); -static void invalidate_label_seq_name_graph_cache(Oid relid); +static bool invalidate_label_seq_name_graph_cache(Oid relid); static void flush_label_seq_name_graph_cache(void); static label_cache_data *search_label_name_graph_cache_miss(Name name, @@ -277,6 +281,8 @@ static void invalidate_graph_caches(Datum arg, int cache_id, uint32 hash_value) { Assert(graph_name_cache_hash); + graph_cache_generation++; + /* * Currently, all entries in the graph caches are flushed because * hash_value is for an entry in NAMESPACEOID cache. Since the caches @@ -318,6 +324,63 @@ static void flush_graph_namespace_cache(void) create_graph_namespace_cache(); } +uint64 get_graph_cache_generation(void) +{ + initialize_caches(); + + return graph_cache_generation; +} + +graph_cache_data *search_graph_name_cache_cached(const char *name) +{ + static NameData cached_name; + static uint64 cached_generation = 0; + static graph_cache_data *cached_graph = NULL; + uint64 current_generation = get_graph_cache_generation(); + + Assert(name); + + if (cached_graph != NULL && + namestrcmp(&cached_name, name) == 0 && + cached_generation == current_generation) + { + return cached_graph; + } + + cached_graph = search_graph_name_cache(name); + if (cached_graph != NULL) + { + namestrcpy(&cached_name, name); + cached_generation = get_graph_cache_generation(); + } + + return cached_graph; +} + +graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) +{ + static Oid cached_namespace = InvalidOid; + static uint64 cached_generation = 0; + static graph_cache_data *cached_graph = NULL; + uint64 current_generation = get_graph_cache_generation(); + + if (cached_graph != NULL && + cached_namespace == namespace && + cached_generation == current_generation) + { + return cached_graph; + } + + cached_graph = search_graph_namespace_cache(namespace); + if (cached_graph != NULL) + { + cached_namespace = namespace; + cached_generation = get_graph_cache_generation(); + } + + return cached_graph; +} + graph_cache_data *search_graph_name_cache(const char *name) { NameData name_key; @@ -607,16 +670,23 @@ static void invalidate_label_caches(Datum arg, Oid relid) Assert(label_name_graph_cache_hash); Assert(label_seq_name_graph_cache_hash); - if (OidIsValid(relid)) { - invalidate_label_name_graph_cache(relid); - invalidate_label_graph_oid_cache(relid); - invalidate_label_relation_cache(relid); - invalidate_label_seq_name_graph_cache(relid); + bool changed = false; + + changed |= invalidate_label_name_graph_cache(relid); + changed |= invalidate_label_graph_oid_cache(relid); + changed |= invalidate_label_relation_cache(relid); + changed |= invalidate_label_seq_name_graph_cache(relid); + + if (changed) + { + label_cache_generation++; + } } else { + label_cache_generation++; flush_label_name_graph_cache(); flush_label_graph_oid_cache(); flush_label_relation_cache(); @@ -624,9 +694,10 @@ static void invalidate_label_caches(Datum arg, Oid relid) } } -static void invalidate_label_name_graph_cache(Oid relid) +static bool invalidate_label_name_graph_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_name_graph_cache_hash); for (;;) @@ -653,8 +724,11 @@ static void invalidate_label_name_graph_cache(Oid relid) (errmsg_internal("label (name, graph) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_name_graph_cache(void) @@ -673,9 +747,10 @@ static void flush_label_name_graph_cache(void) create_label_name_graph_cache(); } -static void invalidate_label_graph_oid_cache(Oid relid) +static bool invalidate_label_graph_oid_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_graph_oid_cache_hash); for (;;) @@ -702,8 +777,11 @@ static void invalidate_label_graph_oid_cache(Oid relid) (errmsg_internal("label (graph, id) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_graph_oid_cache(void) @@ -722,7 +800,7 @@ static void flush_label_graph_oid_cache(void) create_label_graph_oid_cache(); } -static void invalidate_label_relation_cache(Oid relid) +static bool invalidate_label_relation_cache(Oid relid) { label_relation_cache_entry *entry; void *removed; @@ -730,7 +808,7 @@ static void invalidate_label_relation_cache(Oid relid) entry = hash_search(label_relation_cache_hash, &relid, HASH_FIND, NULL); if (!entry) { - return; + return false; } removed = hash_search(label_relation_cache_hash, &relid, HASH_REMOVE, NULL); @@ -738,6 +816,8 @@ static void invalidate_label_relation_cache(Oid relid) { ereport(ERROR, (errmsg_internal("label (namespace) cache corrupted"))); } + + return true; } static void flush_label_relation_cache(void) @@ -756,9 +836,10 @@ static void flush_label_relation_cache(void) create_label_relation_cache(); } -static void invalidate_label_seq_name_graph_cache(Oid relid) +static bool invalidate_label_seq_name_graph_cache(Oid relid) { HASH_SEQ_STATUS hash_seq; + bool changed = false; hash_seq_init(&hash_seq, label_seq_name_graph_cache_hash); for (;;) @@ -785,8 +866,11 @@ static void invalidate_label_seq_name_graph_cache(Oid relid) (errmsg_internal("label (seq_name, graph) cache corrupted"))); } + changed = true; break; } + + return changed; } static void flush_label_seq_name_graph_cache(void) @@ -805,6 +889,104 @@ static void flush_label_seq_name_graph_cache(void) create_label_seq_name_graph_cache(); } +uint64 get_label_cache_generation(void) +{ + initialize_caches(); + + return label_cache_generation; +} + +label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) +{ + static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static int32 cached_ids[LAST_LABEL_CACHE_SIZE] = {-1, -1, -1, -1}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] != NULL && + cached_graphs[i] == graph && + cached_ids[i] == id && + cached_generations[i] == current_generation) + { + return cached_labels[i]; + } + } + + label = search_label_graph_oid_cache(graph, id); + if (label != NULL) + { + int slot = 0; + + for (i = 1; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] == NULL || + cached_generations[i] < cached_generations[slot]) + { + slot = i; + } + } + + cached_graphs[slot] = graph; + cached_ids[slot] = id; + cached_generations[slot] = get_label_cache_generation(); + cached_labels[slot] = label; + } + + return label; +} + +label_cache_data *search_label_name_graph_cache_cached(const char *name, + Oid graph) +{ + static NameData cached_names[LAST_LABEL_CACHE_SIZE]; + static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int i; + + Assert(name); + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] != NULL && + cached_graphs[i] == graph && + namestrcmp(&cached_names[i], name) == 0 && + cached_generations[i] == current_generation) + { + return cached_labels[i]; + } + } + + label = search_label_name_graph_cache(name, graph); + if (label != NULL) + { + int slot = 0; + + for (i = 1; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] == NULL || + cached_generations[i] < cached_generations[slot]) + { + slot = i; + } + } + + namestrcpy(&cached_names[slot], name); + cached_graphs[slot] = graph; + cached_generations[slot] = get_label_cache_generation(); + cached_labels[slot] = label; + } + + return label; +} + label_cache_data *search_label_name_graph_cache(const char *name, Oid graph) { NameData name_key; diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index 6ce627c4d..ec292f42f 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -20,12 +20,19 @@ #include "postgres.h" #include "access/genam.h" +#include "access/table.h" #include "commands/graph_commands.h" #include "utils/load/age_load.h" int64 get_nextval_internal(graph_cache_data* graph_cache, label_cache_data* label_cache); +static void queue_vertex_insert(batch_insert_state *batch_state, + graphid vertex_id, + agtype *vertex_properties); +static void queue_edge_insert(batch_insert_state *batch_state, + graphid edge_id, graphid start_id, + graphid end_id, agtype *edge_properties); /* * Auxiliary function to get the next internal value in the graph, * so a new object (node or edge) graph id can be composed. @@ -44,6 +51,66 @@ int64 get_nextval_internal(graph_cache_data* graph_cache, return nextval_internal(obj_seq_id, true); } +static void queue_vertex_insert(batch_insert_state *batch_state, + graphid vertex_id, + agtype *vertex_properties) +{ + TupleTableSlot *slot; + + slot = batch_state->slots[batch_state->num_tuples]; + ExecClearTuple(slot); + + slot->tts_values[0] = GRAPHID_GET_DATUM(vertex_id); + slot->tts_values[1] = AGTYPE_P_GET_DATUM(vertex_properties); + slot->tts_isnull[0] = false; + slot->tts_isnull[1] = false; + + ExecStoreVirtualTuple(slot); + + batch_state->buffered_bytes += VARSIZE(vertex_properties); + batch_state->num_tuples++; + + if (batch_state->num_tuples >= BATCH_SIZE || + batch_state->buffered_bytes >= MAX_BUFFERED_BYTES) + { + insert_batch(batch_state); + batch_state->num_tuples = 0; + batch_state->buffered_bytes = 0; + } +} + +static void queue_edge_insert(batch_insert_state *batch_state, + graphid edge_id, graphid start_id, + graphid end_id, agtype *edge_properties) +{ + TupleTableSlot *slot; + + slot = batch_state->slots[batch_state->num_tuples]; + ExecClearTuple(slot); + + slot->tts_values[0] = GRAPHID_GET_DATUM(edge_id); + slot->tts_values[1] = GRAPHID_GET_DATUM(start_id); + slot->tts_values[2] = GRAPHID_GET_DATUM(end_id); + slot->tts_values[3] = AGTYPE_P_GET_DATUM(edge_properties); + slot->tts_isnull[0] = false; + slot->tts_isnull[1] = false; + slot->tts_isnull[2] = false; + slot->tts_isnull[3] = false; + + ExecStoreVirtualTuple(slot); + + batch_state->buffered_bytes += VARSIZE(edge_properties); + batch_state->num_tuples++; + + if (batch_state->num_tuples >= BATCH_SIZE || + batch_state->buffered_bytes >= MAX_BUFFERED_BYTES) + { + insert_batch(batch_state); + batch_state->num_tuples = 0; + batch_state->buffered_bytes = 0; + } +} + PG_FUNCTION_INFO_V1(create_complete_graph); /* @@ -78,6 +145,10 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) graph_cache_data *graph_cache; label_cache_data *vertex_cache; label_cache_data *edge_cache; + batch_insert_state *vtx_batch_state = NULL; + batch_insert_state *edge_batch_state = NULL; + Relation vtx_rel; + Relation edge_rel; Oid nsp_id; Name vtx_seq_name; @@ -127,21 +198,30 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) } } - if (!graph_exists(graph_name_str)) + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) { DirectFunctionCall1(create_graph, CStringGetDatum(graph_name->data)); + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" was not found after creation", + graph_name_str))); + } } - graph_oid = get_graph_oid(graph_name_str); - - graph_cache = search_graph_name_cache(graph_name_str); - vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); + graph_oid = graph_cache->oid; + vertex_cache = search_label_name_graph_cache_cached(vtx_name_str, + graph_oid); if (vertex_cache == NULL) { DirectFunctionCall2(create_vlabel, CStringGetDatum(graph_name->data), CStringGetDatum(vtx_name_str)); - vertex_cache = search_label_name_graph_cache(vtx_name_str, graph_oid); + vertex_cache = search_label_name_graph_cache_cached(vtx_name_str, + graph_oid); if (vertex_cache == NULL) { ereport(ERROR, @@ -151,13 +231,14 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) } } - edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + edge_cache = search_label_name_graph_cache_cached(edge_name_str, graph_oid); if (edge_cache == NULL) { DirectFunctionCall2(create_elabel, CStringGetDatum(graph_name->data), CStringGetDatum(edge_label_name->data)); - edge_cache = search_label_name_graph_cache(edge_name_str, graph_oid); + edge_cache = search_label_name_graph_cache_cached(edge_name_str, + graph_oid); if (edge_cache == NULL) { ereport(ERROR, @@ -182,16 +263,24 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) props = create_empty_agtype(); + vtx_rel = table_open(vertex_cache->relation, RowExclusiveLock); + init_batch_insert(&vtx_batch_state, vertex_cache->relation); + /* Creating vertices*/ for (i=(int64)1; i<=no_vertices; i++) { vid = nextval_internal(vtx_seq_id, true); object_graph_id = make_graphid(vtx_label_id, vid); - insert_vertex_simple(graph_oid, vtx_name_str, object_graph_id, props); + queue_vertex_insert(vtx_batch_state, object_graph_id, props); } + finish_batch_insert(&vtx_batch_state); + table_close(vtx_rel, RowExclusiveLock); lid = vid; + edge_rel = table_open(edge_cache->relation, RowExclusiveLock); + init_batch_insert(&edge_batch_state, edge_cache->relation); + /* Creating edges*/ for (i = 1; i<=no_vertices-1; i++) { @@ -205,11 +294,13 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) start_vertex_graph_id = make_graphid(vtx_label_id, start_vid); end_vertex_graph_id = make_graphid(vtx_label_id, end_vid); - insert_edge_simple(graph_oid, edge_name_str, object_graph_id, - start_vertex_graph_id, end_vertex_graph_id, - props); + queue_edge_insert(edge_batch_state, object_graph_id, + start_vertex_graph_id, end_vertex_graph_id, + props); } } + finish_batch_insert(&edge_batch_state); + table_close(edge_rel, RowExclusiveLock); PG_RETURN_VOID(); } @@ -328,14 +419,21 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) arguments->args[5].value, arguments->args[3].value); - graph_oid = get_graph_oid(graph_name_str); - /* * Fetching caches to get next values for graph id's, and access nodes * to be connected with edges. */ - graph_cache = search_graph_name_cache(graph_name_str); - node_cache = search_label_name_graph_cache(node_label_str, graph_oid); + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", + graph_name_str))); + } + + graph_oid = graph_cache->oid; + node_cache = search_label_name_graph_cache_cached(node_label_str, + graph_oid); if (node_cache == NULL) { ereport(ERROR, @@ -344,7 +442,8 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) node_label_str))); } - edge_cache = search_label_name_graph_cache(edge_label_str, graph_oid); + edge_cache = search_label_name_graph_cache_cached(edge_label_str, + graph_oid); if (edge_cache == NULL) { ereport(ERROR, diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index 7b552ebfa..5e1ee7b4d 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -153,7 +153,8 @@ static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, { label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, + graph_oid); entry->id = label_cache != NULL ? label_cache->id : -1; } @@ -190,11 +191,12 @@ int create_edges_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, char *label_name, + Oid label_relid, + Oid label_seq_relid, int label_id, bool load_as_agtype) { Relation label_rel; - Oid label_relid; CopyFromState cstate; List *copy_options; ParseState *pstate; @@ -203,12 +205,9 @@ int create_edges_from_csv_file(char *file_path, char **header = NULL; int header_count = 0; bool is_first_row = true; - char *label_seq_name; - Oid label_seq_relid; batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; - label_cache_data *label_cache; HTAB *vertex_label_id_cache = NULL; /* Create a memory context for batch processing - reset after each batch */ @@ -216,15 +215,8 @@ int create_edges_from_csv_file(char *file_path, "AGE CSV Edge Load Batch Context", ALLOCSET_DEFAULT_SIZES); - /* Get the label relation */ - label_cache = search_label_name_graph_cache(label_name, graph_oid); - label_relid = label_cache != NULL ? label_cache->relation : InvalidOid; label_rel = table_open(label_relid, RowExclusiveLock); - /* Get sequence info */ - label_seq_name = get_label_seq_relation_name(label_name); - label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - /* Initialize the batch insert state */ init_batch_insert(&batch_state, label_relid); vertex_label_id_cache = create_vertex_label_id_cache(); diff --git a/src/backend/utils/load/ag_load_labels.c b/src/backend/utils/load/ag_load_labels.c index 971fcbc54..68ec22d82 100644 --- a/src/backend/utils/load/ag_load_labels.c +++ b/src/backend/utils/load/ag_load_labels.c @@ -130,12 +130,13 @@ int create_labels_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, char *label_name, + Oid label_relid, + Oid label_seq_relid, int label_id, bool id_field_exists, bool load_as_agtype) { Relation label_rel; - Oid label_relid; CopyFromState cstate; List *copy_options; ParseState *pstate; @@ -144,28 +145,18 @@ int create_labels_from_csv_file(char *file_path, char **header = NULL; int header_count = 0; bool is_first_row = true; - char *label_seq_name; - Oid label_seq_relid; int64 curr_seq_num = 0; batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; - label_cache_data *label_cache; /* Create a memory context for batch processing - reset after each batch */ batch_context = AllocSetContextCreate(CurrentMemoryContext, "AGE CSV Load Batch Context", ALLOCSET_DEFAULT_SIZES); - /* Get the label relation */ - label_cache = search_label_name_graph_cache(label_name, graph_oid); - label_relid = label_cache != NULL ? label_cache->relation : InvalidOid; label_rel = table_open(label_relid, RowExclusiveLock); - /* Get sequence info */ - label_seq_name = get_label_seq_relation_name(label_name); - label_seq_relid = get_relname_relid(label_seq_name, graph_oid); - if (id_field_exists) { /* diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 728fa9d4d..839eb61fc 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -426,7 +426,7 @@ void insert_edge_simple(Oid graph_oid, char *label_name, graphid edge_id, HeapTuple tuple; label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label provided exists as vertex label, then throw error */ if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) @@ -478,7 +478,7 @@ void insert_vertex_simple(Oid graph_oid, char *label_name, graphid vertex_id, HeapTuple tuple; label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label provided exists as edge label, then throw error */ if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) @@ -581,6 +581,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) char* file_path_str; Oid graph_oid; Oid label_relid; + Oid label_seq_relid; int32 label_id; bool id_field_exists; bool load_as_agtype; @@ -630,12 +631,14 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) /* Get the label relation and check permissions */ label_relid = label_cache->relation; + label_seq_relid = get_relname_relid(NameStr(label_cache->seq_name), + graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); create_labels_from_csv_file(file_path_str, graph_name_str, graph_oid, - label_name_str, label_id, id_field_exists, - load_as_agtype); + label_name_str, label_relid, label_seq_relid, + label_id, id_field_exists, load_as_agtype); free(file_path_str); @@ -653,6 +656,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) char* file_path_str; Oid graph_oid; Oid label_relid; + Oid label_seq_relid; int32 label_id; bool load_as_agtype; label_cache_data *label_cache; @@ -700,11 +704,14 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) /* Get the label relation and check permissions */ label_relid = label_cache->relation; + label_seq_relid = get_relname_relid(NameStr(label_cache->seq_name), + graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); create_edges_from_csv_file(file_path_str, graph_name_str, graph_oid, - label_name_str, label_id, load_as_agtype); + label_name_str, label_relid, label_seq_relid, + label_id, load_as_agtype); free(file_path_str); @@ -745,7 +752,7 @@ static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, { label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); /* Check if label exists */ if (label_cache != NULL) @@ -775,7 +782,8 @@ static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, parent = list_make1(rv); create_label(graph_name, label_name, label_kind, parent); - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, + graph_oid); if (label_cache == NULL) { ereport(ERROR, diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 0e2c3a91f..3215b4024 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -41,10 +41,17 @@ typedef struct label_cache_data /* callers of these functions must not modify the returned struct */ graph_cache_data *search_graph_name_cache(const char *name); +graph_cache_data *search_graph_name_cache_cached(const char *name); graph_cache_data *search_graph_namespace_cache(Oid namespace); +graph_cache_data *search_graph_namespace_cache_cached(Oid namespace); +uint64 get_graph_cache_generation(void); +uint64 get_label_cache_generation(void); label_cache_data *search_label_oid_cache(Oid oid); label_cache_data *search_label_name_graph_cache(const char *name, Oid graph); +label_cache_data *search_label_name_graph_cache_cached(const char *name, + Oid graph); label_cache_data *search_label_graph_oid_cache(Oid graph, int32 id); +label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph); diff --git a/src/include/utils/age_global_graph.h b/src/include/utils/age_global_graph.h index 325527f48..85b698649 100644 --- a/src/include/utils/age_global_graph.h +++ b/src/include/utils/age_global_graph.h @@ -21,6 +21,7 @@ #define AG_AGE_GLOBAL_GRAPH_H #include "utils/age_graphid_ds.h" +#include "utils/hsearch.h" /* * We declare the graph nodes and edges here, and in this way, so that it may be @@ -43,6 +44,7 @@ GRAPH_global_context *find_GRAPH_global_context(Oid graph_oid); bool is_ggctx_invalid(GRAPH_global_context *ggctx); /* GRAPH retrieval functions */ ListGraphId *get_graph_vertices(GRAPH_global_context *ggctx); +int64 get_graph_num_loaded_edges(GRAPH_global_context *ggctx); vertex_entry *get_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id); edge_entry *get_edge_entry(GRAPH_global_context *ggctx, graphid edge_id); @@ -54,13 +56,19 @@ ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve); Oid get_vertex_entry_label_table_oid(vertex_entry *ve); char *get_vertex_entry_label_name(vertex_entry *ve); Datum get_vertex_entry_properties(vertex_entry *ve); +Datum get_vertex_entry_properties_with_cache(vertex_entry *ve, + HTAB *relation_cache); /* edge entry accessor functions */ graphid get_edge_entry_id(edge_entry *ee); Oid get_edge_entry_label_table_oid(edge_entry *ee); char *get_edge_entry_label_name(edge_entry *ee); Datum get_edge_entry_properties(edge_entry *ee); +Datum get_edge_entry_properties_with_cache(edge_entry *ee, + HTAB *relation_cache); graphid get_edge_entry_start_vertex_id(edge_entry *ee); graphid get_edge_entry_end_vertex_id(edge_entry *ee); +HTAB *create_entry_property_relation_cache(const char *name); +void destroy_entry_property_relation_cache(HTAB *relation_cache); /* Graph version counter functions — shared memory (DSM or shmem) */ uint64 get_graph_version(Oid graph_oid); diff --git a/src/include/utils/load/ag_load_edges.h b/src/include/utils/load/ag_load_edges.h index 4db00d93a..42c95d071 100644 --- a/src/include/utils/load/ag_load_edges.h +++ b/src/include/utils/load/ag_load_edges.h @@ -32,13 +32,16 @@ * graph_name - Name of the graph * graph_oid - OID of the graph * label_name - Name of the edge label + * label_relid - OID of the edge label relation + * label_seq_relid - OID of the edge label sequence relation * label_id - ID of the label * load_as_agtype - If true, parse CSV values as agtype (JSON-like) * * Returns EXIT_SUCCESS on success. */ int create_edges_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, - char *label_name, int label_id, + char *label_name, Oid label_relid, + Oid label_seq_relid, int label_id, bool load_as_agtype); #endif /* AG_LOAD_EDGES_H */ diff --git a/src/include/utils/load/ag_load_labels.h b/src/include/utils/load/ag_load_labels.h index c3d517f30..c3656e12b 100644 --- a/src/include/utils/load/ag_load_labels.h +++ b/src/include/utils/load/ag_load_labels.h @@ -31,6 +31,8 @@ * graph_name - Name of the graph * graph_oid - OID of the graph * label_name - Name of the vertex label + * label_relid - OID of the vertex label relation + * label_seq_relid - OID of the vertex label sequence relation * label_id - ID of the label * id_field_exists - If true, first CSV column contains the vertex ID * load_as_agtype - If true, parse CSV values as agtype (JSON-like) @@ -38,7 +40,8 @@ * Returns EXIT_SUCCESS on success. */ int create_labels_from_csv_file(char *file_path, char *graph_name, Oid graph_oid, - char *label_name, int label_id, + char *label_name, Oid label_relid, + Oid label_seq_relid, int label_id, bool id_field_exists, bool load_as_agtype); #endif /* AG_LOAD_LABELS_H */ From 20e919170e5bb322f8dc5362a5719b615f2b7240 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:37 +0900 Subject: [PATCH 05/15] Harden catalog cache invalidation Consolidate AGE catalog, namespace, type, function, label-sequence, and btree index lookup caches with narrower invalidation rules. The change keeps common OID lookups close to the call sites that need them but flushes them on catalog, function, type, index, and sequence changes that can make the cached values stale. It also preserves Cypher custom-plan cost, width, disabled-node, and parameterization metadata while sharing common custom path initialization. --- src/backend/catalog/ag_catalog.c | 129 ++++++++++- src/backend/catalog/ag_label.c | 20 +- src/backend/catalog/ag_namespace.c | 47 +++- src/backend/optimizer/cypher_createplan.c | 57 +++-- src/backend/optimizer/cypher_pathnode.c | 123 ++++------ src/backend/optimizer/cypher_paths.c | 25 ++ src/backend/parser/cypher_analyze.c | 22 +- src/backend/parser/cypher_clause.c | 61 ++++- src/backend/parser/cypher_expr.c | 133 ++++++++++- src/backend/utils/adt/age_global_graph.c | 2 +- src/backend/utils/adt/agtype.c | 155 +++++++++++-- src/backend/utils/adt/graphid.c | 28 +++ src/backend/utils/ag_func.c | 62 +++-- src/backend/utils/cache/ag_cache.c | 267 +++++++++++++++++++--- src/backend/utils/graph_generation.c | 22 +- src/backend/utils/load/age_load.c | 6 +- src/include/utils/ag_cache.h | 3 + 17 files changed, 921 insertions(+), 241 deletions(-) diff --git a/src/backend/catalog/ag_catalog.c b/src/backend/catalog/ag_catalog.c index 79beeb42e..6c7e70562 100644 --- a/src/backend/catalog/ag_catalog.c +++ b/src/backend/catalog/ag_catalog.c @@ -27,7 +27,12 @@ #include "commands/defrem.h" #include "nodes/parsenodes.h" #include "tcop/utility.h" +#include "utils/builtins.h" +#include "utils/catcache.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -37,6 +42,14 @@ static object_access_hook_type prev_object_access_hook; static ProcessUtility_hook_type prev_process_utility_hook; static bool prev_object_hook_is_set; +static HTAB *ag_relation_id_cache = NULL; +static bool ag_relation_id_callback_registered = false; + +typedef struct ag_relation_id_cache_entry +{ + NameData name; + Oid oid; +} ag_relation_id_cache_entry; static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, int sub_id, void *arg); @@ -47,6 +60,10 @@ void ag_ProcessUtility_hook(PlannedStmt *pstmt, const char *queryString, bool re static bool is_age_drop(PlannedStmt *pstmt); static void drop_age_extension(DropStmt *stmt); +static void initialize_ag_relation_id_cache(void); +static void invalidate_ag_relation_id_cache(Datum arg, Oid relid); +static int ag_relation_name_hash_compare(const void *key1, const void *key2, + Size keysize); void object_access_hook_init(void) { @@ -234,7 +251,7 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, if (drop_arg->dropflags & PERFORM_DELETION_INTERNAL) return; - cache_data = search_graph_namespace_cache(object_id); + cache_data = search_graph_namespace_cache_cached(object_id); if (cache_data) { char *nspname = get_namespace_name(object_id); @@ -251,7 +268,7 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, { label_cache_data *cache_data; - cache_data = search_label_relation_cache(object_id); + cache_data = search_label_relation_cache_cached(object_id); /* We are interested in only tables that are labels. */ if (!cache_data) @@ -280,6 +297,18 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, Oid ag_relation_id(const char *name, const char *kind) { Oid id; + NameData name_key; + ag_relation_id_cache_entry *entry; + bool found; + + initialize_ag_relation_id_cache(); + + namestrcpy(&name_key, name); + entry = hash_search(ag_relation_id_cache, &name_key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->oid; + } id = get_relname_relid(name, ag_catalog_namespace_id()); if (!OidIsValid(id)) @@ -288,5 +317,101 @@ Oid ag_relation_id(const char *name, const char *kind) errmsg("%s \"%s\" does not exist", kind, name))); } + if (ag_relation_id_cache == NULL) + { + initialize_ag_relation_id_cache(); + } + + entry = hash_search(ag_relation_id_cache, &name_key, HASH_ENTER, &found); + if (found) + { + return entry->oid; + } + + entry->oid = id; + return id; } + +static void initialize_ag_relation_id_cache(void) +{ + HASHCTL hash_ctl; + + if (ag_relation_id_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(NameData); + hash_ctl.entrysize = sizeof(ag_relation_id_cache_entry); + hash_ctl.match = ag_relation_name_hash_compare; + hash_ctl.hcxt = CacheMemoryContext; + + ag_relation_id_cache = hash_create("ag_catalog relation oid cache", 8, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_COMPARE | + HASH_CONTEXT); + + if (!ag_relation_id_callback_registered) + { + CacheRegisterRelcacheCallback(invalidate_ag_relation_id_cache, + (Datum)0); + ag_relation_id_callback_registered = true; + } +} + +static void invalidate_ag_relation_id_cache(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS hash_seq; + ag_relation_id_cache_entry *entry; + + if (ag_relation_id_cache == NULL) + { + return; + } + + if (!OidIsValid(relid)) + { + hash_destroy(ag_relation_id_cache); + ag_relation_id_cache = NULL; + return; + } + + hash_seq_init(&hash_seq, ag_relation_id_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->oid == relid) + { + void *removed; + + removed = hash_search(ag_relation_id_cache, &entry->name, + HASH_REMOVE, NULL); + hash_seq_term(&hash_seq); + + if (!removed) + { + ereport(ERROR, + (errmsg_internal("ag relation oid cache corrupted"))); + } + + break; + } + } +} + +static int ag_relation_name_hash_compare(const void *key1, const void *key2, + Size keysize) +{ + Name name1 = (Name)key1; + Name name2 = (Name)key2; + + Assert(keysize == NAMEDATALEN); + + return strncmp(NameStr(*name1), NameStr(*name2), NAMEDATALEN); +} diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 4f99db51f..653d58281 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -126,7 +126,7 @@ int32 get_label_id(const char *label_name, Oid graph_oid) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, graph_oid); + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); if (cache_data) return cache_data->id; else @@ -137,7 +137,7 @@ Oid get_label_relation(const char *label_name, Oid graph_oid) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, graph_oid); + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); if (cache_data) return cache_data->relation; else @@ -153,7 +153,7 @@ char get_label_kind(const char *label_name, Oid label_graph) { label_cache_data *cache_data; - cache_data = search_label_name_graph_cache(label_name, label_graph); + cache_data = search_label_name_graph_cache_cached(label_name, label_graph); if (cache_data) { return cache_data->kind; @@ -199,18 +199,16 @@ Datum _label_name(PG_FUNCTION_ARGS) label_id = (int32)(((uint64)AG_GETARG_GRAPHID(1)) >> ENTRY_ID_BITS); - label_cache = search_label_graph_oid_cache(graph, label_id); - - label_name = NameStr(label_cache->name); - - /* If label_name is not found, error out */ - if (label_name == NULL) + label_cache = search_label_graph_oid_cache_cached(graph, label_id); + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("label with id %d does not exist in graph %u", label_id, graph))); } + label_name = NameStr(label_cache->name); + if (IS_AG_DEFAULT_LABEL(label_name)) PG_RETURN_CSTRING(""); @@ -260,7 +258,7 @@ bool label_id_exists(Oid graph_oid, int32 label_id) { label_cache_data *cache_data; - cache_data = search_label_graph_oid_cache(graph_oid, label_id); + cache_data = search_label_graph_oid_cache_cached(graph_oid, label_id); if (cache_data) return true; else @@ -276,7 +274,7 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, char *relname; label_cache_data *label_cache; - label_cache = search_label_name_graph_cache(label_name, graph_oid); + label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); relname = get_rel_name(label_cache->relation); diff --git a/src/backend/catalog/ag_namespace.c b/src/backend/catalog/ag_namespace.c index f074609cd..36732deea 100644 --- a/src/backend/catalog/ag_namespace.c +++ b/src/backend/catalog/ag_namespace.c @@ -20,15 +20,58 @@ #include "postgres.h" #include "catalog/namespace.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_namespace.h" +static Oid cached_ag_catalog_namespace = InvalidOid; +static Oid cached_pg_catalog_namespace = InvalidOid; +static bool namespace_cache_callback_registered = false; + +static void invalidate_namespace_oid_cache(Datum arg, int cache_id, + uint32 hash_value); +static void initialize_namespace_oid_cache(void); + Oid ag_catalog_namespace_id(void) { - return get_namespace_oid("ag_catalog", false); + initialize_namespace_oid_cache(); + + if (!OidIsValid(cached_ag_catalog_namespace)) + { + cached_ag_catalog_namespace = get_namespace_oid("ag_catalog", false); + } + + return cached_ag_catalog_namespace; } Oid pg_catalog_namespace_id(void) { - return get_namespace_oid("pg_catalog", false); + initialize_namespace_oid_cache(); + + if (!OidIsValid(cached_pg_catalog_namespace)) + { + cached_pg_catalog_namespace = get_namespace_oid("pg_catalog", false); + } + + return cached_pg_catalog_namespace; +} + +static void initialize_namespace_oid_cache(void) +{ + if (namespace_cache_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(NAMESPACEOID, invalidate_namespace_oid_cache, + (Datum)0); + namespace_cache_callback_registered = true; +} + +static void invalidate_namespace_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + cached_ag_catalog_namespace = InvalidOid; + cached_pg_catalog_namespace = InvalidOid; } diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c index 288da05d7..df9495a32 100644 --- a/src/backend/optimizer/cypher_createplan.c +++ b/src/backend/optimizer/cypher_createplan.c @@ -31,23 +31,39 @@ const CustomScanMethods cypher_delete_plan_methods = { const CustomScanMethods cypher_merge_plan_methods = { "Cypher Merge", create_cypher_merge_plan_state}; -Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) +static int get_custom_plan_width(CustomPath *best_path) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); + if (best_path->path.pathtarget == NULL) + return 0; - cs = makeNode(CustomScan); + return best_path->path.pathtarget->width; +} +static void apply_custom_plan_metadata(CustomScan *cs, CustomPath *best_path) +{ cs->scan.plan.startup_cost = best_path->path.startup_cost; cs->scan.plan.total_cost = best_path->path.total_cost; +#if PG_VERSION_NUM >= 170000 + cs->scan.plan.disabled_nodes = best_path->path.disabled_nodes; +#endif cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; + cs->scan.plan.plan_width = get_custom_plan_width(best_path); cs->scan.plan.parallel_aware = best_path->path.parallel_aware; cs->scan.plan.parallel_safe = best_path->path.parallel_safe; +} + +Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + CustomScan *cs; + Plan *subplan = linitial(custom_plans); + + cs = makeNode(CustomScan); + + apply_custom_plan_metadata(cs, best_path); /* Set later in set_plan_refs */ cs->scan.plan.plan_node_id = 0; @@ -83,14 +99,7 @@ Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, cs = makeNode(CustomScan); - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; + apply_custom_plan_metadata(cs, best_path); cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ cs->scan.plan.targetlist = tlist; @@ -130,14 +139,7 @@ Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, cs = makeNode(CustomScan); - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; + apply_custom_plan_metadata(cs, best_path); cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ /* @@ -191,14 +193,7 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, cs = makeNode(CustomScan); - cs->scan.plan.startup_cost = best_path->path.startup_cost; - cs->scan.plan.total_cost = best_path->path.total_cost; - - cs->scan.plan.plan_rows = best_path->path.rows; - cs->scan.plan.plan_width = 0; - - cs->scan.plan.parallel_aware = best_path->path.parallel_aware; - cs->scan.plan.parallel_safe = best_path->path.parallel_safe; + apply_custom_plan_metadata(cs, best_path); cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ /* diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 4d9e96f41..78c729425 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -26,6 +26,7 @@ #include "parser/cypher_analyze.h" #include "executor/cypher_utils.h" #include "optimizer/optimizer.h" +#include "optimizer/pathnode.h" #include "optimizer/subselect.h" #include "nodes/makefuncs.h" @@ -33,6 +34,7 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private); static bool expr_has_sublink(Node *node, void *context); static Path *select_best_child_path(RelOptInfo *rel); +static void initialize_cypher_dml_path(CustomPath *cp, RelOptInfo *rel); static void apply_child_path_costs(CustomPath *cp, Path *best_child); const CustomPathMethods cypher_create_path_methods = { @@ -51,27 +53,8 @@ CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, cp = makeNode(CustomPath); - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->custom_paths = list_make1(select_best_child_path(rel)); - apply_child_path_costs(cp, linitial(cp->custom_paths)); - - /* No output ordering for basic CREATE */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ + initialize_cypher_dml_path(cp, rel); cp->flags = 0; - cp->custom_private = custom_private; cp->methods = &cypher_create_path_methods; @@ -85,27 +68,8 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, cp = makeNode(CustomPath); - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->custom_paths = list_make1(select_best_child_path(rel)); - apply_child_path_costs(cp, linitial(cp->custom_paths)); - - /* No output ordering for basic SET */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ + initialize_cypher_dml_path(cp, rel); cp->flags = 0; - cp->custom_private = custom_private; cp->methods = &cypher_set_path_methods; @@ -123,27 +87,8 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, cp = makeNode(CustomPath); - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->custom_paths = list_make1(select_best_child_path(rel)); - apply_child_path_costs(cp, linitial(cp->custom_paths)); - - /* No output ordering for basic SET */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ + initialize_cypher_dml_path(cp, rel); cp->flags = 0; - /* Store the metadata Delete will need in the execution phase. */ cp->custom_private = custom_private; /* Tells Postgres how to turn this path to the correct CustomScan */ @@ -163,25 +108,7 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, cp = makeNode(CustomPath); - cp->path.pathtype = T_CustomScan; - - cp->path.parent = rel; - cp->path.pathtarget = rel->reltarget; - - cp->path.param_info = NULL; - - /* Do not allow parallel methods */ - cp->path.parallel_aware = false; - cp->path.parallel_safe = false; - cp->path.parallel_workers = 0; - - cp->custom_paths = list_make1(select_best_child_path(rel)); - apply_child_path_costs(cp, linitial(cp->custom_paths)); - - /* No output ordering for basic SET */ - cp->path.pathkeys = NULL; - - /* Disable all custom flags for now */ + initialize_cypher_dml_path(cp, rel); cp->flags = 0; /* @@ -204,6 +131,30 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, return cp; } +static void initialize_cypher_dml_path(CustomPath *cp, RelOptInfo *rel) +{ + Path *best_child; + + cp->path.pathtype = T_CustomScan; + + cp->path.parent = rel; + cp->path.pathtarget = rel->reltarget; + + cp->path.param_info = NULL; + + /* Do not allow parallel methods */ + cp->path.parallel_aware = false; + cp->path.parallel_safe = false; + cp->path.parallel_workers = 0; + + best_child = select_best_child_path(rel); + cp->custom_paths = list_make1(best_child); + apply_child_path_costs(cp, best_child); + + /* Cypher DML nodes do not preserve child output ordering. */ + cp->path.pathkeys = NULL; +} + /* * Deserializes the merge information and checks if any property * expression (prop_expr) contains a SubLink. @@ -271,9 +222,7 @@ static Path *select_best_child_path(RelOptInfo *rel) Path *child = (Path *)lfirst(lc); if (best_child == NULL || - child->total_cost < best_child->total_cost || - (child->total_cost == best_child->total_cost && - child->startup_cost < best_child->startup_cost)) + compare_path_costs(child, best_child, TOTAL_COST) < 0) best_child = child; } @@ -287,6 +236,10 @@ static void apply_child_path_costs(CustomPath *cp, Path *best_child) { if (best_child == NULL) { + cp->path.param_info = NULL; +#if PG_VERSION_NUM >= 170000 + cp->path.disabled_nodes = 0; +#endif cp->path.rows = 0; cp->path.startup_cost = 0; cp->path.total_cost = 0; @@ -298,9 +251,15 @@ static void apply_child_path_costs(CustomPath *cp, Path *best_child) * child paths are PostgreSQL-planned scans/subqueries. Preserve their * estimates so upstream planning can see row counts, index selectivity, * and scan costs instead of treating every Cypher DML clause as free. + * Preserve the child parameterization as well so add_path() can compare + * required outer relids using the same information as the child path. * Charge one cpu_tuple_cost per input row for the mutation coordination * work that happens above the child plan. */ + cp->path.param_info = best_child->param_info; +#if PG_VERSION_NUM >= 170000 + cp->path.disabled_nodes = best_child->disabled_nodes; +#endif cp->path.rows = best_child->rows; cp->path.startup_cost = best_child->startup_cost; cp->path.total_cost = best_child->total_cost + diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index 0b567993a..71284eccd 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -21,6 +21,8 @@ #include "optimizer/pathnode.h" #include "optimizer/paths.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "optimizer/cypher_pathnode.h" #include "optimizer/cypher_paths.h" @@ -41,11 +43,14 @@ static Oid cypher_create_clause_func_oid = InvalidOid; static Oid cypher_set_clause_func_oid = InvalidOid; static Oid cypher_delete_clause_func_oid = InvalidOid; static Oid cypher_merge_clause_func_oid = InvalidOid; +static bool cypher_clause_func_callback_registered = false; static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); static void init_cypher_clause_function_oids(void); +static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value); static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, @@ -135,6 +140,17 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) static void init_cypher_clause_function_oids(void) { + if (!cypher_clause_func_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, + invalidate_cypher_clause_function_oids, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_cypher_clause_function_oids, + (Datum)0); + cypher_clause_func_callback_registered = true; + } + if (OidIsValid(cypher_create_clause_func_oid)) { return; @@ -150,6 +166,15 @@ static void init_cypher_clause_function_oids(void) get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } +static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value) +{ + cypher_create_clause_func_oid = InvalidOid; + cypher_set_clause_func_oid = InvalidOid; + cypher_delete_clause_func_oid = InvalidOid; + cypher_merge_clause_func_oid = InvalidOid; +} + /* replace all possible paths with our CustomPath */ static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte) diff --git a/src/backend/parser/cypher_analyze.c b/src/backend/parser/cypher_analyze.c index b3a55edbe..e72c8c889 100644 --- a/src/backend/parser/cypher_analyze.c +++ b/src/backend/parser/cypher_analyze.c @@ -27,6 +27,8 @@ #include "parser/parse_relation.h" #include "parser/parse_target.h" #include "utils/builtins.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_graph.h" #include "parser/cypher_analyze.h" @@ -49,12 +51,16 @@ static Node *extra_node = NULL; static void build_explain_query(Query *query, Node *explain_node); static post_parse_analyze_hook_type prev_post_parse_analyze_hook; +static Oid cypher_func_oid = InvalidOid; +static bool cypher_func_oid_callback_registered = false; static void post_parse_analyze(ParseState *pstate, Query *query, JumbleState *jstate); static bool convert_cypher_walker(Node *node, ParseState *pstate); static bool is_rte_cypher(RangeTblEntry *rte); static bool is_func_cypher(FuncExpr *funcexpr); static Oid get_cypher_func_oid(void); +static void invalidate_cypher_func_oid(Datum arg, int cache_id, + uint32 hash_value); static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate); static Name expr_get_const_name(Node *expr); static const char *expr_get_const_cstring(Node *expr, const char *source_str); @@ -377,7 +383,15 @@ static bool is_func_cypher(FuncExpr *funcexpr) static Oid get_cypher_func_oid(void) { - static Oid cypher_func_oid = InvalidOid; + if (!cypher_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_cypher_func_oid, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_cypher_func_oid, + (Datum)0); + cypher_func_oid_callback_registered = true; + } if (!OidIsValid(cypher_func_oid)) { @@ -388,6 +402,12 @@ static Oid get_cypher_func_oid(void) return cypher_func_oid; } +static void invalidate_cypher_func_oid(Datum arg, int cache_id, + uint32 hash_value) +{ + cypher_func_oid = InvalidOid; +} + /* convert cypher() call to SELECT subquery in-place */ static void convert_cypher_to_subquery(RangeTblEntry *rte, ParseState *pstate) { diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 0ae3ebf67..13b597a17 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -40,6 +40,8 @@ #include "parser/parsetree.h" #include "parser/parse_relation.h" #include "rewrite/rewriteHandler.h" +#include "utils/inval.h" +#include "utils/syscache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -96,6 +98,13 @@ typedef Query *(*transform_method)(cypher_parsestate *cpstate, cypher_clause *clause); +static Oid create_clause_func_oid = InvalidOid; +static Oid set_clause_func_oid = InvalidOid; +static Oid delete_clause_func_oid = InvalidOid; +static Oid merge_clause_func_oid = InvalidOid; +static Oid bool_or_func_oid = InvalidOid; +static bool clause_func_oid_callback_registered = false; + /* projection */ static Query *transform_cypher_return(cypher_parsestate *cpstate, cypher_clause *clause); @@ -338,7 +347,11 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, RangeTblEntry *rte); static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); +static void initialize_clause_function_oid_cache(void); static Oid get_clause_function_oid(const char *function_name); +static Oid get_bool_or_func_oid(void); +static void invalidate_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value); static FuncExpr *make_clause_func_expr(char *function_name, Node *clause_information); static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); @@ -1682,19 +1695,13 @@ static Node *make_bool_or_agg(ParseState *pstate, Node *arg) { Aggref *agg; TargetEntry *te; - Oid bool_or_oid; - Oid argtypes[1] = { BOOLOID }; - - /* Look up bool_or(boolean) */ - bool_or_oid = LookupFuncName(list_make1(makeString("bool_or")), - 1, argtypes, false); /* Build the TargetEntry for the aggregate argument */ te = makeTargetEntry((Expr *) arg, 1, NULL, false); /* Construct the Aggref */ agg = makeNode(Aggref); - agg->aggfnoid = bool_or_oid; + agg->aggfnoid = get_bool_or_func_oid(); agg->aggtype = BOOLOID; agg->aggcollid = InvalidOid; agg->inputcollid = InvalidOid; @@ -8369,10 +8376,7 @@ static FuncExpr *make_clause_func_expr(char *function_name, static Oid get_clause_function_oid(const char *function_name) { - static Oid create_clause_func_oid = InvalidOid; - static Oid set_clause_func_oid = InvalidOid; - static Oid delete_clause_func_oid = InvalidOid; - static Oid merge_clause_func_oid = InvalidOid; + initialize_clause_function_oid_cache(); if (!OidIsValid(create_clause_func_oid)) { @@ -8406,6 +8410,41 @@ static Oid get_clause_function_oid(const char *function_name) return get_ag_func_oid(function_name, 1, INTERNALOID); } +static Oid get_bool_or_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(bool_or_func_oid)) + { + bool_or_func_oid = get_pg_func_oid("bool_or", 1, BOOLOID); + } + + return bool_or_func_oid; +} + +static void initialize_clause_function_oid_cache(void) +{ + if (!clause_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_clause_function_oids, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_clause_function_oids, + (Datum)0); + clause_func_oid_callback_registered = true; + } +} + +static void invalidate_clause_function_oids(Datum arg, int cache_id, + uint32 hash_value) +{ + create_clause_func_oid = InvalidOid; + set_clause_func_oid = InvalidOid; + delete_clause_func_oid = InvalidOid; + merge_clause_func_oid = InvalidOid; + bool_or_func_oid = InvalidOid; +} + /* * This function is borrowed from PG version 16.1. * diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 92a9bda2a..8cf35ac02 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -41,8 +41,10 @@ #include "utils/catcache.h" #include "utils/float.h" #include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" +#include "utils/syscache.h" #include "parser/cypher_expr.h" #include "parser/cypher_transform_entity.h" @@ -73,7 +75,16 @@ typedef struct function_exists_cache_entry bool exists; } function_exists_cache_entry; +typedef struct function_extension_cache_entry +{ + Oid func_oid; + bool has_extension; + NameData extension; +} function_extension_cache_entry; + static HTAB *function_exists_cache = NULL; +static HTAB *function_extension_cache = NULL; +static bool function_cache_callback_registered = false; static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); @@ -128,6 +139,10 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); static char *get_mapped_extension(Oid func_oid); static bool is_extension_external(char *extension); static char *construct_age_function_name(char *funcname); +static void initialize_function_caches(void); +static void invalidate_function_caches(Datum arg, int cache_id, + uint32 hash_value); +static void initialize_function_extension_cache(void); static void initialize_function_exists_cache(void); static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, @@ -1852,6 +1867,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) { CatCList *catlist = NULL; Form_pg_proc procform = NULL; + Form_pg_proc result = NULL; int nargs; int i = 0; List *asp; @@ -1901,6 +1917,8 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) if (found) { + result = palloc(sizeof(FormData_pg_proc)); + memcpy(result, procform, sizeof(FormData_pg_proc)); break; } @@ -1909,7 +1927,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) } /* Error out if function not found */ - if (err_not_found && (procform == NULL)) + if (err_not_found && (result == NULL)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_FUNCTION), @@ -1923,17 +1941,47 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) ReleaseSysCacheList(catlist); pfree_if_not_null(asp); - return procform; + return result; } static char *get_mapped_extension(Oid func_oid) { Oid extension_oid; char *extension = NULL; + function_extension_cache_entry *entry; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + if (!entry->has_extension) + { + return NULL; + } + return pstrdup(NameStr(entry->extension)); + } extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); extension = get_extension_name(extension_oid); + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension); + } + } + return extension; } @@ -1989,9 +2037,8 @@ static bool function_exists(char *funcname, char *extension) namestrcpy(&key.funcname, funcname); namestrcpy(&key.extension, extension); - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - if (cache_found) + entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); + if (entry != NULL) { return entry->exists; } @@ -2007,8 +2054,14 @@ static bool function_exists(char *funcname, char *extension) if (catlist->n_members == 0) { ReleaseSysCacheList(catlist); - if (entry != NULL) + if (extension != NULL) { + if (function_exists_cache == NULL) + { + initialize_function_exists_cache(); + } + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); entry->exists = false; } return false; @@ -2035,8 +2088,14 @@ static bool function_exists(char *funcname, char *extension) /* we need to release the cache list */ ReleaseSysCacheList(catlist); - if (entry != NULL) + if (extension != NULL) { + if (function_exists_cache == NULL) + { + initialize_function_exists_cache(); + } + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); entry->exists = found; } @@ -2052,10 +2111,7 @@ static void initialize_function_exists_cache(void) return; } - if (!CacheMemoryContext) - { - CreateCacheMemoryContext(); - } + initialize_function_caches(); MemSet(&hash_ctl, 0, sizeof(hash_ctl)); hash_ctl.keysize = sizeof(function_exists_cache_key); @@ -2067,6 +2123,61 @@ static void initialize_function_exists_cache(void) HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } +static void initialize_function_extension_cache(void) +{ + HASHCTL hash_ctl; + + if (function_extension_cache != NULL) + { + return; + } + + initialize_function_caches(); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(function_extension_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_extension_cache = + hash_create("cypher function extension cache", 64, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void initialize_function_caches(void) +{ + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + if (!function_cache_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_function_caches, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_function_caches, + (Datum)0); + function_cache_callback_registered = true; + } +} + +static void invalidate_function_caches(Datum arg, int cache_id, + uint32 hash_value) +{ + if (function_exists_cache != NULL) + { + hash_destroy(function_exists_cache); + function_exists_cache = NULL; + } + + if (function_extension_cache != NULL) + { + hash_destroy(function_extension_cache); + function_extension_cache = NULL; + } +} + /* * Code borrowed from PG's transformFuncCall and updated for AGE */ diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 0acdcc3d6..5b92e1588 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -1990,7 +1990,7 @@ Oid get_graph_oid_for_table(Oid table_oid) { label_cache_data *lcd = NULL; - lcd = search_label_relation_cache(table_oid); + lcd = search_label_relation_cache_cached(table_oid); if (lcd != NULL) { diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 6f94f8c96..16656201e 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -49,10 +49,15 @@ #include "utils/acl.h" #include "utils/ag_cache.h" #include "utils/builtins.h" +#include "utils/catcache.h" #include "executor/cypher_utils.h" #include "utils/float.h" +#include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/relcache.h" +#include "utils/syscache.h" #include "utils/snapmgr.h" #include "utils/typcache.h" #include "utils/age_vle.h" @@ -75,6 +80,18 @@ typedef struct PercentileGroupAggState bool sort_done; } PercentileGroupAggState; +typedef struct btree_index_attr_cache_key +{ + Oid relid; + AttrNumber attnum; +} btree_index_attr_cache_key; + +typedef struct btree_index_attr_cache_entry +{ + btree_index_attr_cache_key key; + Oid index_oid; +} btree_index_attr_cache_entry; + typedef enum /* type categories for datum_to_agtype */ { AGT_TYPE_NULL, /* null, so we didn't bother to identify */ @@ -164,7 +181,10 @@ static bool is_array_path(agtype_value *agtv); static Oid get_cached_graph_oid_for_name(const char *graph_name); static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid); -static Oid get_cached_vertex_id_index_oid(Relation vertex_label_relation); +static void initialize_btree_index_attr_cache(void); +static void invalidate_btree_index_attr_cache(Datum arg, Oid relid); +static Oid find_usable_btree_index_for_attr_uncached(Relation rel, + AttrNumber attnum); static char *get_label_name(const char *graph_name, graphid element_graphid, Oid *label_relation); static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, @@ -217,10 +237,19 @@ void pfree_if_not_null(void *ptr) /* global storage of OID for agtype and _agtype */ static Oid g_AGTYPEOID = InvalidOid; static Oid g_AGTYPEARRAYOID = InvalidOid; +static bool agtype_oid_callback_registered = false; +static HTAB *btree_index_attr_cache = NULL; +static bool btree_index_attr_callback_registered = false; + +static void initialize_agtype_oid_cache(void); +static void invalidate_agtype_oid_cache(Datum arg, int cache_id, + uint32 hash_value); /* helper function to quickly set, if necessary, and retrieve AGTYPEOID */ Oid get_AGTYPEOID(void) { + initialize_agtype_oid_cache(); + if (g_AGTYPEOID == InvalidOid) { g_AGTYPEOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -234,6 +263,8 @@ Oid get_AGTYPEOID(void) /* helper function to quickly set, if necessary, and retrieve AGTYPEARRAYOID */ Oid get_AGTYPEARRAYOID(void) { + initialize_agtype_oid_cache(); + if (g_AGTYPEARRAYOID == InvalidOid) { g_AGTYPEARRAYOID = GetSysCacheOid2(TYPENAMENSP,Anum_pg_type_oid, @@ -251,6 +282,24 @@ void clear_global_Oids_AGTYPE(void) g_AGTYPEARRAYOID = InvalidOid; } +static void initialize_agtype_oid_cache(void) +{ + if (agtype_oid_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(TYPENAMENSP, invalidate_agtype_oid_cache, + (Datum)0); + agtype_oid_callback_registered = true; +} + +static void invalidate_agtype_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + clear_global_Oids_AGTYPE(); +} + /* fast helper function to test for AGTV_NULL in an agtype */ bool is_agtype_null(agtype *agt_arg) { @@ -5708,7 +5757,8 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, /* open the relation (table) */ graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); - index_oid = get_cached_vertex_id_index_oid(graph_vertex_label); + index_oid = find_usable_btree_index_for_attr( + graph_vertex_label, Anum_ag_label_vertex_table_id); if (OidIsValid(index_oid)) { @@ -12191,36 +12241,93 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, return result; } -static Oid get_cached_vertex_id_index_oid(Relation vertex_label_relation) +/* + * Helper function to find a valid index for a specific attribute. + * Returns the OID of the index, or InvalidOid if none is found. + */ +Oid find_usable_btree_index_for_attr(Relation rel, AttrNumber attnum) { - static Oid cached_relid = InvalidOid; - static Oid cached_index_oid = InvalidOid; - static uint64 cached_generation = 0; - static bool cached = false; - Oid relid = RelationGetRelid(vertex_label_relation); - uint64 current_generation = get_label_cache_generation(); + btree_index_attr_cache_key key; + btree_index_attr_cache_entry *entry; + bool found; - if (cached && - cached_relid == relid && - cached_generation == current_generation) + initialize_btree_index_attr_cache(); + + MemSet(&key, 0, sizeof(key)); + key.relid = RelationGetRelid(rel); + key.attnum = attnum; + + entry = hash_search(btree_index_attr_cache, &key, HASH_ENTER, &found); + if (!found) { - return cached_index_oid; + entry->index_oid = + find_usable_btree_index_for_attr_uncached(rel, attnum); } - cached_index_oid = find_usable_btree_index_for_attr( - vertex_label_relation, Anum_ag_label_vertex_table_id); - cached_relid = relid; - cached_generation = get_label_cache_generation(); - cached = true; + return entry->index_oid; +} + +static void initialize_btree_index_attr_cache(void) +{ + HASHCTL hash_ctl; - return cached_index_oid; + if (btree_index_attr_cache != NULL) + { + return; + } + + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(btree_index_attr_cache_key); + hash_ctl.entrysize = sizeof(btree_index_attr_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + btree_index_attr_cache = + hash_create("AGE btree index attribute cache", 32, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + if (!btree_index_attr_callback_registered) + { + CacheRegisterRelcacheCallback(invalidate_btree_index_attr_cache, + (Datum)0); + btree_index_attr_callback_registered = true; + } } -/* - * Helper function to find a valid index for a specific attribute. - * Returns the OID of the index, or InvalidOid if none is found. - */ -Oid find_usable_btree_index_for_attr(Relation rel, AttrNumber attnum) +static void invalidate_btree_index_attr_cache(Datum arg, Oid relid) +{ + HASH_SEQ_STATUS hash_seq; + btree_index_attr_cache_entry *entry; + + if (btree_index_attr_cache == NULL) + { + return; + } + + if (!OidIsValid(relid)) + { + hash_destroy(btree_index_attr_cache); + btree_index_attr_cache = NULL; + return; + } + + hash_seq_init(&hash_seq, btree_index_attr_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->key.relid == relid || entry->index_oid == relid) + { + hash_search(btree_index_attr_cache, &entry->key, HASH_REMOVE, + NULL); + } + } +} + +static Oid find_usable_btree_index_for_attr_uncached(Relation rel, + AttrNumber attnum) { List *index_list; ListCell *ilc; diff --git a/src/backend/utils/adt/graphid.c b/src/backend/utils/adt/graphid.c index 71f41093b..f6c013678 100644 --- a/src/backend/utils/adt/graphid.c +++ b/src/backend/utils/adt/graphid.c @@ -20,6 +20,7 @@ #include "postgres.h" #include "utils/builtins.h" +#include "utils/inval.h" #include "utils/sortsupport.h" #include "utils/graphid.h" @@ -29,10 +30,17 @@ static int graphid_btree_fast_cmp(Datum x, Datum y, SortSupport ssup); /* global storage of OID for graphid and _graphid */ static Oid g_GRAPHIDOID = InvalidOid; static Oid g_GRAPHIDARRAYOID = InvalidOid; +static bool graphid_oid_callback_registered = false; + +static void initialize_graphid_oid_cache(void); +static void invalidate_graphid_oid_cache(Datum arg, int cache_id, + uint32 hash_value); /* helper function to quickly set, if necessary, and retrieve GRAPHIDOID */ Oid get_GRAPHIDOID(void) { + initialize_graphid_oid_cache(); + if (g_GRAPHIDOID == InvalidOid) { g_GRAPHIDOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -46,6 +54,8 @@ Oid get_GRAPHIDOID(void) /* helper function to quickly set, if necessary, and retrieve GRAPHIDARRAYOID */ Oid get_GRAPHIDARRAYOID(void) { + initialize_graphid_oid_cache(); + if (g_GRAPHIDARRAYOID == InvalidOid) { g_GRAPHIDARRAYOID = GetSysCacheOid2(TYPENAMENSP, Anum_pg_type_oid, @@ -63,6 +73,24 @@ void clear_global_Oids_GRAPHID(void) g_GRAPHIDARRAYOID = InvalidOid; } +static void initialize_graphid_oid_cache(void) +{ + if (graphid_oid_callback_registered) + { + return; + } + + CacheRegisterSyscacheCallback(TYPENAMENSP, invalidate_graphid_oid_cache, + (Datum)0); + graphid_oid_callback_registered = true; +} + +static void invalidate_graphid_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + clear_global_Oids_GRAPHID(); +} + PG_FUNCTION_INFO_V1(graphid_in); /* graphid type input function */ diff --git a/src/backend/utils/ag_func.c b/src/backend/utils/ag_func.c index 0d22699ab..17d9c9ca5 100644 --- a/src/backend/utils/ag_func.c +++ b/src/backend/utils/ag_func.c @@ -29,6 +29,7 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" @@ -50,8 +51,11 @@ typedef struct ag_func_cache_entry } ag_func_cache_entry; static HTAB *ag_func_oid_cache = NULL; +static bool ag_func_oid_callback_registered = false; static void initialize_ag_func_oid_cache(void); +static void invalidate_ag_func_oid_cache(Datum arg, int cache_id, + uint32 hash_value); /* checks that func_oid is of func_name function in ag_catalog */ bool is_oid_ag_func(Oid func_oid, const char *func_name) @@ -86,6 +90,8 @@ Oid get_ag_func_oid(const char *func_name, const int nargs, ...) { ag_func_cache_key key; ag_func_cache_entry *entry; + oidvector *arg_types; + Oid func_oid; va_list ap; int i; bool found; @@ -103,22 +109,32 @@ Oid get_ag_func_oid(const char *func_name, const int nargs, ...) key.args[i] = va_arg(ap, Oid); va_end(ap); + entry = hash_search(ag_func_oid_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->func_oid; + } + + arg_types = buildoidvector(key.args, nargs); + func_oid = GetSysCacheOid3(PROCNAMEARGSNSP, Anum_pg_proc_oid, + CStringGetDatum(func_name), + PointerGetDatum(arg_types), + ObjectIdGetDatum(ag_catalog_namespace_id())); + if (!OidIsValid(func_oid)) + { + ereport(ERROR, (errmsg_internal("ag function does not exist"), + errdetail_internal("%s(%d)", func_name, nargs))); + } + + if (ag_func_oid_cache == NULL) + { + initialize_ag_func_oid_cache(); + } + entry = hash_search(ag_func_oid_cache, &key, HASH_ENTER, &found); if (!found) { - oidvector *arg_types; - - arg_types = buildoidvector(key.args, nargs); - entry->func_oid = GetSysCacheOid3( - PROCNAMEARGSNSP, Anum_pg_proc_oid, - CStringGetDatum(func_name), - PointerGetDatum(arg_types), - ObjectIdGetDatum(ag_catalog_namespace_id())); - if (!OidIsValid(entry->func_oid)) - { - ereport(ERROR, (errmsg_internal("ag function does not exist"), - errdetail_internal("%s(%d)", func_name, nargs))); - } + entry->func_oid = func_oid; } return entry->func_oid; @@ -145,6 +161,26 @@ static void initialize_ag_func_oid_cache(void) ag_func_oid_cache = hash_create("ag function oid cache", 32, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + if (!ag_func_oid_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_ag_func_oid_cache, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_ag_func_oid_cache, + (Datum)0); + ag_func_oid_callback_registered = true; + } +} + +static void invalidate_ag_func_oid_cache(Datum arg, int cache_id, + uint32 hash_value) +{ + if (ag_func_oid_cache != NULL) + { + hash_destroy(ag_func_oid_cache); + ag_func_oid_cache = NULL; + } } Oid get_pg_func_oid(const char *func_name, const int nargs, ...) diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index eb0b8f510..5379b37f4 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -25,11 +25,13 @@ #include "utils/builtins.h" #include "utils/catcache.h" #include "utils/inval.h" +#include "utils/lsyscache.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" #include "utils/ag_cache.h" +#define LAST_GRAPH_CACHE_SIZE 4 #define LAST_LABEL_CACHE_SIZE 4 typedef struct graph_name_cache_entry @@ -99,6 +101,15 @@ static ScanKeyData graph_namespace_scan_keys[1]; static HTAB *label_name_graph_cache_hash = NULL; static ScanKeyData label_name_graph_scan_keys[2]; static uint64 label_cache_generation = 1; +static uint64 label_seq_relation_cache_generation = 1; +static NameData label_seq_relation_cached_names[LAST_LABEL_CACHE_SIZE]; +static Oid label_seq_relation_cached_namespaces[LAST_LABEL_CACHE_SIZE] = + {InvalidOid}; +static uint64 label_seq_relation_cached_generations[LAST_LABEL_CACHE_SIZE] = + {0}; +static Oid label_seq_relation_cached_relations[LAST_LABEL_CACHE_SIZE] = + {InvalidOid}; +static int label_seq_relation_next_slot = 0; /* ag_label.graph, ag_label.id */ static HTAB *label_graph_oid_cache_hash = NULL; @@ -151,6 +162,7 @@ static bool invalidate_label_relation_cache(Oid relid); static void flush_label_relation_cache(void); static bool invalidate_label_seq_name_graph_cache(Oid relid); static void flush_label_seq_name_graph_cache(void); +static bool invalidate_label_seq_relation_cache(Oid relid); static label_cache_data *search_label_name_graph_cache_miss(Name name, Oid graph); @@ -333,52 +345,98 @@ uint64 get_graph_cache_generation(void) graph_cache_data *search_graph_name_cache_cached(const char *name) { - static NameData cached_name; - static uint64 cached_generation = 0; - static graph_cache_data *cached_graph = NULL; + static NameData cached_names[LAST_GRAPH_CACHE_SIZE]; + static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; + static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static int next_slot = 0; uint64 current_generation = get_graph_cache_generation(); + int slot; + int i; Assert(name); - if (cached_graph != NULL && - namestrcmp(&cached_name, name) == 0 && - cached_generation == current_generation) + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (cached_graphs[i] != NULL && + cached_generations[i] == current_generation && + namestrcmp(&cached_names[i], name) == 0) + { + return cached_graphs[i]; + } + } + + slot = -1; + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (cached_graphs[i] == NULL || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) { - return cached_graph; + slot = next_slot; + next_slot = (next_slot + 1) % LAST_GRAPH_CACHE_SIZE; } - cached_graph = search_graph_name_cache(name); - if (cached_graph != NULL) + cached_graphs[slot] = search_graph_name_cache(name); + if (cached_graphs[slot] != NULL) { - namestrcpy(&cached_name, name); - cached_generation = get_graph_cache_generation(); + namestrcpy(&cached_names[slot], name); + cached_generations[slot] = get_graph_cache_generation(); } - return cached_graph; + return cached_graphs[slot]; } graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) { - static Oid cached_namespace = InvalidOid; - static uint64 cached_generation = 0; - static graph_cache_data *cached_graph = NULL; + static Oid cached_namespaces[LAST_GRAPH_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; + static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static int next_slot = 0; uint64 current_generation = get_graph_cache_generation(); + int slot; + int i; - if (cached_graph != NULL && - cached_namespace == namespace && - cached_generation == current_generation) + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) { - return cached_graph; + if (cached_graphs[i] != NULL && + cached_generations[i] == current_generation && + cached_namespaces[i] == namespace) + { + return cached_graphs[i]; + } + } + + slot = -1; + for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) + { + if (cached_graphs[i] == NULL || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_GRAPH_CACHE_SIZE; } - cached_graph = search_graph_namespace_cache(namespace); - if (cached_graph != NULL) + cached_graphs[slot] = search_graph_namespace_cache(namespace); + if (cached_graphs[slot] != NULL) { - cached_namespace = namespace; - cached_generation = get_graph_cache_generation(); + cached_namespaces[slot] = namespace; + cached_generations[slot] = get_graph_cache_generation(); } - return cached_graph; + return cached_graphs[slot]; } graph_cache_data *search_graph_name_cache(const char *name) @@ -673,20 +731,27 @@ static void invalidate_label_caches(Datum arg, Oid relid) if (OidIsValid(relid)) { bool changed = false; + bool seq_relation_changed; changed |= invalidate_label_name_graph_cache(relid); changed |= invalidate_label_graph_oid_cache(relid); changed |= invalidate_label_relation_cache(relid); changed |= invalidate_label_seq_name_graph_cache(relid); + seq_relation_changed = invalidate_label_seq_relation_cache(relid); if (changed) { label_cache_generation++; } + if (changed || seq_relation_changed) + { + label_seq_relation_cache_generation++; + } } else { label_cache_generation++; + label_seq_relation_cache_generation++; flush_label_name_graph_cache(); flush_label_graph_oid_cache(); flush_label_relation_cache(); @@ -889,6 +954,25 @@ static void flush_label_seq_name_graph_cache(void) create_label_seq_name_graph_cache(); } +static bool invalidate_label_seq_relation_cache(Oid relid) +{ + bool changed = false; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (label_seq_relation_cached_relations[i] == relid) + { + label_seq_relation_cached_relations[i] = InvalidOid; + label_seq_relation_cached_namespaces[i] = InvalidOid; + label_seq_relation_cached_generations[i] = 0; + changed = true; + } + } + + return changed; +} + uint64 get_label_cache_generation(void) { initialize_caches(); @@ -902,6 +986,7 @@ label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) static int32 cached_ids[LAST_LABEL_CACHE_SIZE] = {-1, -1, -1, -1}; static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static int next_slot = 0; uint64 current_generation = get_label_cache_generation(); label_cache_data *label; int i; @@ -920,17 +1005,24 @@ label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) label = search_label_graph_oid_cache(graph, id); if (label != NULL) { - int slot = 0; + int slot = -1; - for (i = 1; i < LAST_LABEL_CACHE_SIZE; i++) + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { if (cached_labels[i] == NULL || - cached_generations[i] < cached_generations[slot]) + cached_generations[i] != current_generation) { slot = i; + break; } } + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + cached_graphs[slot] = graph; cached_ids[slot] = id; cached_generations[slot] = get_label_cache_generation(); @@ -947,6 +1039,7 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static int next_slot = 0; uint64 current_generation = get_label_cache_generation(); label_cache_data *label; int i; @@ -967,17 +1060,24 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, label = search_label_name_graph_cache(name, graph); if (label != NULL) { - int slot = 0; + int slot = -1; - for (i = 1; i < LAST_LABEL_CACHE_SIZE; i++) + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { if (cached_labels[i] == NULL || - cached_generations[i] < cached_generations[slot]) + cached_generations[i] != current_generation) { slot = i; + break; } } + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + namestrcpy(&cached_names[slot], name); cached_graphs[slot] = graph; cached_generations[slot] = get_label_cache_generation(); @@ -1162,6 +1262,55 @@ label_cache_data *search_label_relation_cache(Oid relation) return search_label_relation_cache_miss(relation); } +label_cache_data *search_label_relation_cache_cached(Oid relation) +{ + static Oid cached_relations[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; + static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; + static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static int next_slot = 0; + uint64 current_generation = get_label_cache_generation(); + label_cache_data *label; + int i; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] != NULL && + cached_relations[i] == relation && + cached_generations[i] == current_generation) + { + return cached_labels[i]; + } + } + + label = search_label_relation_cache(relation); + if (label != NULL) + { + int slot = -1; + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (cached_labels[i] == NULL || + cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + cached_relations[slot] = relation; + cached_generations[slot] = get_label_cache_generation(); + cached_labels[slot] = label; + } + + return label; +} + static label_cache_data *search_label_relation_cache_miss(Oid relation) { ScanKeyData scan_keys[1]; @@ -1230,6 +1379,64 @@ label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph) return search_label_seq_name_graph_cache_miss(&name_key, graph); } +Oid get_label_seq_relation_cached(const label_cache_data *label_cache, + Oid namespace) +{ + uint64 current_generation = label_seq_relation_cache_generation; + const char *seq_name; + Oid seq_relid; + int slot; + int i; + + Assert(label_cache != NULL); + Assert(OidIsValid(namespace)); + + seq_name = NameStr(label_cache->seq_name); + + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (OidIsValid(label_seq_relation_cached_relations[i]) && + label_seq_relation_cached_namespaces[i] == namespace && + label_seq_relation_cached_generations[i] == current_generation && + namestrcmp(&label_seq_relation_cached_names[i], seq_name) == 0) + { + return label_seq_relation_cached_relations[i]; + } + } + + seq_relid = get_relname_relid(seq_name, namespace); + if (!OidIsValid(seq_relid)) + { + return InvalidOid; + } + + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) + { + if (!OidIsValid(label_seq_relation_cached_relations[i]) || + label_seq_relation_cached_generations[i] != current_generation) + { + slot = i; + break; + } + } + + if (slot < 0) + { + slot = label_seq_relation_next_slot; + label_seq_relation_next_slot = + (label_seq_relation_next_slot + 1) % LAST_LABEL_CACHE_SIZE; + } + + namestrcpy(&label_seq_relation_cached_names[slot], seq_name); + label_seq_relation_cached_namespaces[slot] = namespace; + label_seq_relation_cached_generations[slot] = + label_seq_relation_cache_generation; + label_seq_relation_cached_relations[slot] = seq_relid; + + return seq_relid; +} + static label_cache_data *search_label_seq_name_graph_cache_miss(Name name, Oid graph) { diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index ec292f42f..784e1e882 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -42,11 +42,9 @@ int64 get_nextval_internal(graph_cache_data* graph_cache, label_cache_data* label_cache) { Oid obj_seq_id; - char* label_seq_name_str; - label_seq_name_str = NameStr(label_cache->seq_name); - obj_seq_id = get_relname_relid(label_seq_name_str, - graph_cache->namespace); + obj_seq_id = get_label_seq_relation_cached(label_cache, + graph_cache->namespace); return nextval_internal(obj_seq_id, true); } @@ -151,12 +149,6 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) Relation edge_rel; Oid nsp_id; - Name vtx_seq_name; - char *vtx_seq_name_str; - - Name edge_seq_name; - char *edge_seq_name_str; - int64 lid; if (PG_ARGISNULL(0)) @@ -252,14 +244,8 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) edge_label_id = edge_cache->id; nsp_id = graph_cache->namespace; - vtx_seq_name = &(vertex_cache->seq_name); - vtx_seq_name_str = NameStr(*vtx_seq_name); - - edge_seq_name = &(edge_cache->seq_name); - edge_seq_name_str = NameStr(*edge_seq_name); - - vtx_seq_id = get_relname_relid(vtx_seq_name_str, nsp_id); - edge_seq_id = get_relname_relid(edge_seq_name_str, nsp_id); + vtx_seq_id = get_label_seq_relation_cached(vertex_cache, nsp_id); + edge_seq_id = get_label_seq_relation_cached(edge_cache, nsp_id); props = create_empty_agtype(); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 839eb61fc..d0532644b 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -631,8 +631,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) /* Get the label relation and check permissions */ label_relid = label_cache->relation; - label_seq_relid = get_relname_relid(NameStr(label_cache->seq_name), - graph_oid); + label_seq_relid = get_label_seq_relation_cached(label_cache, graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); @@ -704,8 +703,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) /* Get the label relation and check permissions */ label_relid = label_cache->relation; - label_seq_relid = get_relname_relid(NameStr(label_cache->seq_name), - graph_oid); + label_seq_relid = get_label_seq_relation_cached(label_cache, graph_oid); check_table_permissions(label_relid); check_rls_for_load(label_relid); diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 3215b4024..2ff01c2c4 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -53,6 +53,9 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, label_cache_data *search_label_graph_oid_cache(Oid graph, int32 id); label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); +label_cache_data *search_label_relation_cache_cached(Oid relation); label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph); +Oid get_label_seq_relation_cached(const label_cache_data *label_cache, + Oid namespace); #endif From b7b65a485643c822bafcaa83553713f773a2df4b Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:37 +0900 Subject: [PATCH 06/15] Reuse DML, graph, and agtype hot-path state Group the next set of executor and expression hot-path caches that reduce repeat work inside common Cypher queries. MERGE update caches, shared btree index lookup, graph-name cache scans, direct agtype field access, endpoint graph-name reuse, SET tuple slots, DETACH DELETE scan slots, and scalar argument type caching all avoid rebuilding state that is stable for the current expression or executor invocation. --- src/backend/catalog/ag_label.c | 7 - src/backend/executor/cypher_create.c | 2 +- src/backend/executor/cypher_delete.c | 48 +++- src/backend/executor/cypher_merge.c | 22 +- src/backend/executor/cypher_set.c | 119 +++++++-- src/backend/executor/cypher_utils.c | 52 +++- src/backend/parser/cypher_clause.c | 181 ++++++++++---- src/backend/parser/cypher_expr.c | 291 +++++++++++++++++++---- src/backend/utils/adt/age_global_graph.c | 85 ++++--- src/backend/utils/adt/age_vle.c | 170 +++++++------ src/backend/utils/adt/agtype.c | 111 ++++++--- src/backend/utils/cache/ag_cache.c | 4 +- src/include/executor/cypher_utils.h | 8 + src/include/utils/age_global_graph.h | 3 + src/include/utils/load/age_load.h | 9 +- 15 files changed, 821 insertions(+), 291 deletions(-) diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 653d58281..c6b20c39d 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -189,13 +189,6 @@ Datum _label_name(PG_FUNCTION_ARGS) } graph = PG_GETARG_OID(0); - - /* Check if the graph OID is valid */ - if (!graph_namespace_exists(graph)) - { - ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("graph with oid %u does not exist", graph))); - } label_id = (int32)(((uint64)AG_GETARG_GRAPHID(1)) >> ENTRY_ID_BITS); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index bc44086f1..4814f775a 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -580,7 +580,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, } /* extract the id agtype field */ - id_value = GET_AGTYPE_VALUE_OBJECT_VALUE(v, "id"); + id_value = AGTYPE_VERTEX_GET_ID(v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index b300e03e5..23b652bac 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -211,7 +211,7 @@ static void end_cypher_delete(CustomScanState *node) if (css->index_cache != NULL) { - hash_destroy(css->index_cache); + destroy_index_cache(css->index_cache, false); css->index_cache = NULL; } @@ -462,7 +462,14 @@ static void process_delete_list(CustomScanState *node) original_entity_value = extract_entity(node, scanTupleSlot, entity_position); - id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); + if (original_entity_value->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(original_entity_value); + } + else + { + id = AGTYPE_EDGE_GET_ID(original_entity_value); + } label_cache = search_label_graph_oid_cache_cached( css->delete_data->graph_oid, GET_LABEL_ID(id->val.int_value)); if (label_cache == NULL) @@ -543,7 +550,16 @@ static void process_delete_list(CustomScanState *node) if (OidIsValid(index_oid)) { - slot = table_slot_create(rel, NULL); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, RowExclusiveLock); index_scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); @@ -603,7 +619,7 @@ static void process_delete_list(CustomScanState *node) if (OidIsValid(index_oid)) { - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); index_endscan(index_scan_desc); index_close(index_rel, RowExclusiveLock); } @@ -624,6 +640,7 @@ static void process_edges_by_index(Oid index_oid, Relation rel, EState *estate, cypher_delete_custom_scan_state *css, + IndexCacheEntry *idx_entry, ResultRelInfo *resultRelInfo, Oid relid, char *label_name, @@ -641,7 +658,16 @@ static void process_edges_by_index(Oid index_oid, ScanKeyData key; TupleTableSlot *slot; - slot = table_slot_create(rel, NULL); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, RowExclusiveLock); scan = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); @@ -732,7 +758,7 @@ static void process_edges_by_index(Oid index_oid, } index_endscan(scan); index_close(index_rel, RowExclusiveLock); - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); } static void ensure_detach_delete_rls(CustomScanState *node, @@ -838,14 +864,16 @@ static void check_for_connected_edges(CustomScanState *node) if (OidIsValid(start_index_oid) && OidIsValid(end_index_oid)) { /* PASS 1: Find edges where the deleted vertex is the START_ID. */ - process_edges_by_index(start_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, &rls_checked, + process_edges_by_index(start_index_oid, rel, estate, css, + idx_entry, resultRelInfo, relid, + label_name, &rls_checked, &rls_enabled, &qualExprs, &econtext, false, &delete_acl_checked); /* PASS 2: Find edges where the deleted vertex is the END_ID. */ - process_edges_by_index(end_index_oid, rel, estate, css, resultRelInfo, - relid, label_name, &rls_checked, + process_edges_by_index(end_index_oid, rel, estate, css, + idx_entry, resultRelInfo, relid, + label_name, &rls_checked, &rls_enabled, &qualExprs, &econtext, true, &delete_acl_checked); } diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 84b149ff1..2bd0d1175 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -507,7 +507,7 @@ static path_entry **prebuild_path(CustomScanState *node) } /* extract the id agtype field */ - agtv_id = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "id"); + agtv_id = AGTYPE_VERTEX_GET_ID(agtv_vertex); /* set the necessary entry fields - actual & id */ entry->actual = true; @@ -1065,6 +1065,24 @@ static void end_cypher_merge(CustomScanState *node) css->entity_exists_index_cache = NULL; } + if (css->update_qual_cache != NULL) + { + hash_destroy(css->update_qual_cache); + css->update_qual_cache = NULL; + } + + if (css->update_index_cache != NULL) + { + destroy_index_cache(css->update_index_cache, false); + css->update_index_cache = NULL; + } + + if (css->update_result_rel_info_cache != NULL) + { + destroy_entity_result_rel_info_cache(css->update_result_rel_info_cache); + css->update_result_rel_info_cache = NULL; + } + foreach (lc, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc); @@ -1422,7 +1440,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, } /* extract the id agtype field */ - id_value = GET_AGTYPE_VALUE_OBJECT_VALUE(v, "id"); + id_value = AGTYPE_VERTEX_GET_ID(v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index b641cdc48..81396162a 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -45,6 +45,9 @@ static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, HTAB **result_rel_info_cache, const char *qual_name, const char *index_name, const char *result_rel_info_name); +static void init_update_lookup_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name); const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, begin_cypher_set, @@ -117,6 +120,16 @@ static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, HTAB **result_rel_info_cache, const char *qual_name, const char *index_name, const char *result_rel_info_name) +{ + init_update_lookup_caches(qual_cache, index_cache, qual_name, index_name); + + *result_rel_info_cache = + create_entity_result_rel_info_cache(result_rel_info_name); +} + +static void init_update_lookup_caches(HTAB **qual_cache, HTAB **index_cache, + const char *qual_name, + const char *index_name) { HASHCTL hashctl; HASHCTL idx_hashctl; @@ -134,9 +147,6 @@ static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, idx_hashctl.hcxt = CurrentMemoryContext; *index_cache = hash_create(index_name, 8, &idx_hashctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); - - *result_rel_info_cache = - create_entity_result_rel_info_cache(result_rel_info_name); } static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, @@ -289,7 +299,22 @@ static bool check_path(agtype_value *path, graphid updated_id) { agtype_value *elem = &path->val.array.elems[i]; - agtype_value *id = GET_AGTYPE_VALUE_OBJECT_VALUE(elem, "id"); + agtype_value *id; + + if (elem->type != AGTV_VERTEX && elem->type != AGTV_EDGE) + { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported agtype found in a path"))); + } + + if (elem->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(elem); + } + else + { + id = AGTYPE_EDGE_GET_ID(elem); + } if (updated_id == id->val.int_value) { @@ -340,7 +365,14 @@ static agtype_value *replace_entity_in_path(agtype_value *path, } /* extract the id field */ - id = GET_AGTYPE_VALUE_OBJECT_VALUE(elem, "id"); + if (elem->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(elem); + } + else + { + id = AGTYPE_EDGE_GET_ID(elem); + } /* * Either replace or keep the entity in the new path, depending on the id @@ -448,6 +480,28 @@ void apply_update_list(CustomScanState *node, result_rel_info_cache = css->result_rel_info_cache; graph_oid = css->graph_oid; } + else if (node->methods == &cypher_merge_exec_methods) + { + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + + if (css->update_qual_cache == NULL || + css->update_index_cache == NULL || + css->update_result_rel_info_cache == NULL) + { + init_update_caches(&css->update_qual_cache, + &css->update_index_cache, + &css->update_result_rel_info_cache, + "merge_update_qual_cache", + "merge_update_index_cache", + "merge_update_result_rel_info_cache"); + } + + qual_cache = css->update_qual_cache; + index_cache = css->update_index_cache; + result_rel_info_cache = css->update_result_rel_info_cache; + graph_oid = css->graph_oid; + } else { init_update_caches(&qual_cache, &index_cache, @@ -558,7 +612,18 @@ void apply_update_list(CustomScanState *node, } /* get the id and label metadata for later */ - id = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "id"); + if (original_entity_value->type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(original_entity_value); + original_properties = + AGTYPE_VERTEX_GET_PROPERTIES(original_entity_value); + } + else + { + id = AGTYPE_EDGE_GET_ID(original_entity_value); + original_properties = + AGTYPE_EDGE_GET_PROPERTIES(original_entity_value); + } label_cache = search_label_graph_oid_cache_cached( graph_oid, GET_LABEL_ID(id->val.int_value)); if (label_cache == NULL) @@ -574,10 +639,6 @@ void apply_update_list(CustomScanState *node, label_name = ""; } - /* get the properties we need to update */ - original_properties = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, - "properties"); - /* * Determine if the property should be removed. This will be because * this is a REMOVE clause or the variable references a variable that is @@ -679,8 +740,8 @@ void apply_update_list(CustomScanState *node, } else if (original_entity_value->type == AGTV_EDGE) { - startid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "start_id"); - endid = GET_AGTYPE_VALUE_OBJECT_VALUE(original_entity_value, "end_id"); + startid = AGTYPE_EDGE_GET_START_ID(original_entity_value); + endid = AGTYPE_EDGE_GET_END_ID(original_entity_value); new_entity = make_edge(GRAPHID_GET_DATUM(id->val.int_value), GRAPHID_GET_DATUM(startid->val.int_value), @@ -735,10 +796,6 @@ void apply_update_list(CustomScanState *node, } index_oid = idx_entry->index_oid; - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), - &TTSOpsHeapTuple); - rls_entry = hash_search(qual_cache, &relid, HASH_ENTER, &found_rls_entry); if (!found_rls_entry) @@ -774,6 +831,18 @@ void apply_update_list(CustomScanState *node, rls_entry->withCheckOptionExprs; } + slot = idx_entry->update_slot; + if (slot == NULL) + { + slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), + &TTSOpsHeapTuple); + idx_entry->update_slot = slot; + } + else + { + ExecClearTuple(slot); + } + if (original_entity_value->type == AGTV_VERTEX) { slot = populate_vertex_tts(slot, id, altered_properties); @@ -799,7 +868,17 @@ void apply_update_list(CustomScanState *node, ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_GRAPHIDEQ, GRAPHID_GET_DATUM(id->val.int_value)); - index_slot = table_slot_create(rel, NULL); + index_slot = idx_entry->slot; + if (index_slot == NULL) + { + index_slot = table_slot_create(rel, NULL); + idx_entry->slot = index_slot; + } + else + { + ExecClearTuple(index_slot); + } + idx_scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); index_rescan(idx_scan_desc, scan_keys, 1, NULL, 0); @@ -839,7 +918,7 @@ void apply_update_list(CustomScanState *node, } } - ExecDropSingleTupleTableSlot(index_slot); + ExecClearTuple(index_slot); index_endscan(idx_scan_desc); index_close(index_rel, RowExclusiveLock); } @@ -899,7 +978,7 @@ void apply_update_list(CustomScanState *node, if (local_caches) { hash_destroy(qual_cache); - hash_destroy(index_cache); + destroy_index_cache(index_cache, false); destroy_entity_result_rel_info_cache(result_rel_info_cache); } @@ -979,7 +1058,7 @@ static void end_cypher_set(CustomScanState *node) if (css->index_cache != NULL) { - hash_destroy(css->index_cache); + destroy_index_cache(css->index_cache, false); css->index_cache = NULL; } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index fc1e2d4ee..b2255ad22 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -211,6 +211,11 @@ HTAB *create_entity_exists_index_cache(const char *name) } void destroy_entity_exists_index_cache(HTAB *index_cache) +{ + destroy_index_cache(index_cache, true); +} + +void destroy_index_cache(HTAB *index_cache, bool close_relations) { HASH_SEQ_STATUS hash_seq; IndexCacheEntry *entry; @@ -218,7 +223,11 @@ void destroy_entity_exists_index_cache(HTAB *index_cache) hash_seq_init(&hash_seq, index_cache); while ((entry = hash_seq_search(&hash_seq)) != NULL) { - if (entry->rel != NULL) + if (entry->slot != NULL) + { + ExecDropSingleTupleTableSlot(entry->slot); + } + if (close_relations && entry->rel != NULL) { table_close(entry->rel, AccessShareLock); } @@ -248,6 +257,7 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, TupleTableSlot *slot; Oid index_oid = InvalidOid; CommandId saved_curcid; + IndexCacheEntry *cache_entry = NULL; /* * Extract the label id from the graph id and get the table name @@ -278,29 +288,30 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, if (index_cache != NULL) { - IndexCacheEntry *entry; bool found; - entry = hash_search(index_cache, &label->relation, HASH_ENTER, - &found); + cache_entry = hash_search(index_cache, &label->relation, HASH_ENTER, + &found); if (!found) { - init_index_cache_entry(entry); - entry->rel = table_open(label->relation, AccessShareLock); - rel = entry->rel; - entry->index_oid = find_usable_btree_index_for_attr(rel, 1); - entry->index_oid_cached = true; + init_index_cache_entry(cache_entry); + cache_entry->rel = table_open(label->relation, AccessShareLock); + rel = cache_entry->rel; + cache_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); + cache_entry->index_oid_cached = true; } else { - rel = entry->rel; + rel = cache_entry->rel; } - index_oid = entry->index_oid; + index_oid = cache_entry->index_oid; + slot = cache_entry->slot; } else { rel = table_open(label->relation, AccessShareLock); index_oid = find_usable_btree_index_for_attr(rel, 1); + slot = NULL; } if (OidIsValid(index_oid)) @@ -308,7 +319,18 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, IndexScanDesc index_scan_desc; Relation index_rel; - slot = table_slot_create(rel, NULL); + if (slot == NULL) + { + slot = table_slot_create(rel, NULL); + if (cache_entry != NULL) + { + cache_entry->slot = slot; + } + } + else + { + ExecClearTuple(slot); + } index_rel = index_open(index_oid, AccessShareLock); @@ -322,7 +344,11 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, index_endscan(index_scan_desc); index_close(index_rel, AccessShareLock); - ExecDropSingleTupleTableSlot(slot); + ExecClearTuple(slot); + if (index_cache == NULL) + { + ExecDropSingleTupleTableSlot(slot); + } } else { diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 13b597a17..20e691dc3 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -103,6 +103,13 @@ static Oid set_clause_func_oid = InvalidOid; static Oid delete_clause_func_oid = InvalidOid; static Oid merge_clause_func_oid = InvalidOid; static Oid bool_or_func_oid = InvalidOid; +static Oid materialize_vle_edges_func_oid = InvalidOid; +static Oid build_path_func_oid = InvalidOid; +static Oid build_edge_func_oid = InvalidOid; +static Oid build_vertex_func_oid = InvalidOid; +static Oid label_name_func_oid = InvalidOid; +static Oid volatile_wrapper_func_oid = InvalidOid; +static Oid age_properties_func_oid = InvalidOid; static bool clause_func_oid_callback_registered = false; /* projection */ @@ -350,6 +357,14 @@ static List *make_target_list_from_join(ParseState *pstate, static void initialize_clause_function_oid_cache(void); static Oid get_clause_function_oid(const char *function_name); static Oid get_bool_or_func_oid(void); +static Oid get_materialize_vle_edges_func_oid(void); +static Oid get_build_path_func_oid(void); +static Oid get_build_edge_func_oid(void); +static Oid get_build_vertex_func_oid(void); +static Oid get_label_name_func_oid(void); +static Oid get_volatile_wrapper_func_oid(void); +static Oid get_age_properties_func_oid(void); +static Node *make_age_properties_func_expr(Node *arg); static void invalidate_clause_function_oids(Datum arg, int cache_id, uint32 hash_value); static FuncExpr *make_clause_func_expr(char *function_name, @@ -4934,7 +4949,7 @@ static transform_entity *transform_VLE_edge_entity(cypher_parsestate *cpstate, * edges. For a VLE edge variable we need to return a list of edges, * not a path. */ - func_oid = get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + func_oid = get_materialize_vle_edges_func_oid(); /* build the expr node for the function */ fexpr = makeFuncExpr(func_oid, AGTYPEOID, args, InvalidOid, InvalidOid, @@ -5170,26 +5185,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ else if (prop_var != NULL) { - /* - * Remember that prop_var is already transformed. We need - * to built the transform manually. - */ - FuncCall *fc = NULL; - List *targs = NIL; - List *fname = NIL; - - targs = lappend(targs, prop_var); - fname = list_make2(makeString("ag_catalog"), - makeString("age_properties")); - fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); - - /* - * Hand off to ParseFuncOrColumn to create the function - * expression for properties(prop_var) - */ - prop_expr = ParseFuncOrColumn(pstate, fname, targs, - pstate->p_last_srf, fc, false, - -1); + prop_expr = make_age_properties_func_expr(prop_var); } if (is_ag_node(node->props, cypher_map)) @@ -5302,26 +5298,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ else if (prop_var != NULL) { - /* - * Remember that prop_var is already transformed. We need - * to built the transform manually. - */ - FuncCall *fc = NULL; - List *targs = NIL; - List *fname = NIL; - - targs = lappend(targs, prop_var); - fname = list_make2(makeString("ag_catalog"), - makeString("age_properties")); - fc = makeFuncCall(fname, targs, COERCE_SQL_SYNTAX, -1); - - /* - * Hand off to ParseFuncOrColumn to create the function - * expression for properties(prop_var) - */ - prop_expr = ParseFuncOrColumn(pstate, fname, targs, - pstate->p_last_srf, fc, - false, -1); + prop_expr = make_age_properties_func_expr(prop_var); } if (is_ag_node(rel->props, cypher_map)) @@ -5495,7 +5472,7 @@ transform_match_create_path_variable(cypher_parsestate *cpstate, } /* get the oid for the path creation function */ - build_path_oid = get_ag_func_oid("_agtype_build_path", 1, ANYOID); + build_path_oid = get_build_path_func_oid(); /* * If we have a NULL in the path, there is an invalid label, so there aren't @@ -6188,8 +6165,7 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, FuncExpr *func_expr; FuncExpr *label_name_func_expr; - func_oid = get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, - GRAPHIDOID, CSTRINGOID, AGTYPEOID); + func_oid = get_build_edge_func_oid(); id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_ID, -1); @@ -6197,8 +6173,7 @@ static Node *make_edge_expr(cypher_parsestate *cpstate, end_id = scanNSItemForColumn(pstate, pnsi, 0, AG_EDGE_COLNAME_END_ID, -1); - label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, - GRAPHIDOID); + label_name_func_oid = get_label_name_func_oid(); graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, @@ -6237,13 +6212,11 @@ static Node *make_vertex_expr(cypher_parsestate *cpstate, Assert(pnsi != NULL); - func_oid = get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, - CSTRINGOID, AGTYPEOID); + func_oid = get_build_vertex_func_oid(); id = scanNSItemForColumn(pstate, pnsi, 0, AG_VERTEX_COLNAME_ID, -1); - label_name_func_oid = get_ag_func_oid("_label_name", 2, OIDOID, - GRAPHIDOID); + label_name_func_oid = get_label_name_func_oid(); graph_oid_const = makeConst(OIDOID, -1, InvalidOid, sizeof(Oid), ObjectIdGetDatum(cpstate->graph_oid), false, @@ -7226,7 +7199,7 @@ static Expr *add_volatile_wrapper(Expr *node) ereport(ERROR, (errmsg_internal("add_volatile_wrapper: NULL expr"))); } - oid = get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); + oid = get_volatile_wrapper_func_oid(); /* if the passed Expr node is already wrapped, just return it */ if (IsA(node, FuncExpr) && oid == ((FuncExpr*)node)->funcid) @@ -8422,6 +8395,105 @@ static Oid get_bool_or_func_oid(void) return bool_or_func_oid; } +static Oid get_materialize_vle_edges_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(materialize_vle_edges_func_oid)) + { + materialize_vle_edges_func_oid = + get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + } + + return materialize_vle_edges_func_oid; +} + +static Oid get_build_path_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_path_func_oid)) + { + build_path_func_oid = get_ag_func_oid("_agtype_build_path", 1, ANYOID); + } + + return build_path_func_oid; +} + +static Oid get_build_edge_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_edge_func_oid)) + { + build_edge_func_oid = + get_ag_func_oid("_agtype_build_edge", 5, GRAPHIDOID, GRAPHIDOID, + GRAPHIDOID, CSTRINGOID, AGTYPEOID); + } + + return build_edge_func_oid; +} + +static Oid get_build_vertex_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(build_vertex_func_oid)) + { + build_vertex_func_oid = + get_ag_func_oid("_agtype_build_vertex", 3, GRAPHIDOID, CSTRINGOID, + AGTYPEOID); + } + + return build_vertex_func_oid; +} + +static Oid get_label_name_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(label_name_func_oid)) + { + label_name_func_oid = + get_ag_func_oid("_label_name", 2, OIDOID, GRAPHIDOID); + } + + return label_name_func_oid; +} + +static Oid get_volatile_wrapper_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(volatile_wrapper_func_oid)) + { + volatile_wrapper_func_oid = + get_ag_func_oid("agtype_volatile_wrapper", 1, ANYOID); + } + + return volatile_wrapper_func_oid; +} + +static Oid get_age_properties_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(age_properties_func_oid)) + { + age_properties_func_oid = + get_ag_func_oid("age_properties", 1, AGTYPEOID); + } + + return age_properties_func_oid; +} + +static Node *make_age_properties_func_expr(Node *arg) +{ + return (Node *)makeFuncExpr(get_age_properties_func_oid(), AGTYPEOID, + list_make1(arg), InvalidOid, InvalidOid, + COERCE_SQL_SYNTAX); +} + static void initialize_clause_function_oid_cache(void) { if (!clause_func_oid_callback_registered) @@ -8443,6 +8515,13 @@ static void invalidate_clause_function_oids(Datum arg, int cache_id, delete_clause_func_oid = InvalidOid; merge_clause_func_oid = InvalidOid; bool_or_func_oid = InvalidOid; + materialize_vle_edges_func_oid = InvalidOid; + build_path_func_oid = InvalidOid; + build_edge_func_oid = InvalidOid; + build_vertex_func_oid = InvalidOid; + label_name_func_oid = InvalidOid; + volatile_wrapper_func_oid = InvalidOid; + age_properties_func_oid = InvalidOid; } /* diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 8cf35ac02..482ae74ae 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -85,6 +85,20 @@ typedef struct function_extension_cache_entry static HTAB *function_exists_cache = NULL; static HTAB *function_extension_cache = NULL; static bool function_cache_callback_registered = false; +static Oid agtype_access_operator_oid = InvalidOid; +static Oid agtype_access_slice_oid = InvalidOid; +static Oid agtype_in_operator_oid = InvalidOid; +static Oid agtype_add_oid = InvalidOid; +static Oid agtype_build_empty_list_oid = InvalidOid; +static Oid agtype_build_list_oid = InvalidOid; +static Oid agtype_build_empty_map_oid = InvalidOid; +static Oid agtype_build_map_oid = InvalidOid; +static Oid agtype_build_map_nonull_oid = InvalidOid; +static Oid age_properties_oid = InvalidOid; +static Oid agtype_string_match_starts_with_oid = InvalidOid; +static Oid agtype_string_match_ends_with_oid = InvalidOid; +static Oid agtype_string_match_contains_oid = InvalidOid; +static Oid text_to_agtype_oid = InvalidOid; static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); @@ -144,6 +158,20 @@ static void invalidate_function_caches(Datum arg, int cache_id, uint32 hash_value); static void initialize_function_extension_cache(void); static void initialize_function_exists_cache(void); +static Oid get_agtype_access_operator_oid(void); +static Oid get_agtype_access_slice_oid(void); +static Oid get_agtype_in_operator_oid(void); +static Oid get_agtype_add_oid(void); +static Oid get_agtype_build_empty_list_oid(void); +static Oid get_agtype_build_list_oid(void); +static Oid get_agtype_build_empty_map_oid(void); +static Oid get_agtype_build_map_oid(void); +static Oid get_agtype_build_map_nonull_oid(void); +static Oid get_age_properties_oid(void); +static Oid get_agtype_string_match_starts_with_oid(void); +static Oid get_agtype_string_match_ends_with_oid(void); +static Oid get_agtype_string_match_contains_oid(void); +static Oid get_text_to_agtype_oid(void); static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, Oid source_oid, Oid target_oid, @@ -619,8 +647,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) args = lappend(args, transform_cypher_expr_recurse(cpstate, a->lexpr)); /* get the agtype_in_operator function */ - func_in_oid = get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, - AGTYPEOID); + func_in_oid = get_agtype_in_operator_oid(); func_in_expr = makeFuncExpr(func_in_oid, AGTYPEOID, args, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -876,8 +903,7 @@ static Node *transform_cypher_param(cypher_parsestate *cpstate, } /* get the agtype_access_operator function */ - func_access_oid = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); + func_access_oid = get_agtype_access_operator_oid(); args = lappend(args, copyObject(cpstate->params)); @@ -918,7 +944,7 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, */ transformed_map_var = transform_cypher_expr_recurse(cpstate, (Node *)cmp->map_var); - foid_age_properties = get_ag_func_oid("age_properties", 1, AGTYPEOID); + foid_age_properties = get_age_properties_oid(); fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID, list_make1(transformed_map_var), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -962,8 +988,7 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, key_agtype = makeConst(AGTYPEOID, -1, InvalidOid, -1, string_to_agtype(elem->key), false, false); - foid_access_op = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); + foid_access_op = get_agtype_access_operator_oid(); args_access_op = make_agtype_array_expr( list_make2(fexpr_orig_map, key_agtype)); fexpr_access_op = makeFuncExpr(foid_access_op, AGTYPEOID, @@ -1020,8 +1045,7 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, if (keyvals) { - foid_agtype_build_map = get_ag_func_oid("agtype_build_map_nonull", 1, - ANYOID); + foid_agtype_build_map = get_agtype_build_map_nonull_oid(); fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals, InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -1083,21 +1107,21 @@ static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) if (nkeyvals == 0) { - abm_func_oid = get_ag_func_oid("agtype_build_map", 0); + abm_func_oid = get_agtype_build_empty_map_oid(); } else if (!cm->keep_null) { - abm_func_oid = get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + abm_func_oid = get_agtype_build_map_nonull_oid(); } else { - abm_func_oid = get_ag_func_oid("agtype_build_map", 1, ANYOID); + abm_func_oid = get_agtype_build_map_oid(); } /* get the concat function oid, if necessary */ if (nkeyvals > 100) { - aa_func_oid = get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + aa_func_oid = get_agtype_add_oid(); } /* get the key/val list */ @@ -1213,17 +1237,17 @@ static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) nelems = list_length(cl->elems); if (nelems == 0) { - abl_func_oid = get_ag_func_oid("agtype_build_list", 0); + abl_func_oid = get_agtype_build_empty_list_oid(); } else { - abl_func_oid = get_ag_func_oid("agtype_build_list", 1, ANYOID); + abl_func_oid = get_agtype_build_list_oid(); } /* get the concat function oid, if necessary */ if (nelems > 100) { - aa_func_oid = get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + aa_func_oid = get_agtype_add_oid(); } /* iterate through the list of elements */ @@ -1385,11 +1409,9 @@ static Node *transform_A_Indirection(cypher_parsestate *cpstate, /* validate that we have an indirection with at least 1 entry */ Assert(a_ind != NULL && list_length(a_ind->indirection)); /* get the agtype_access_operator function */ - func_access_oid = get_ag_func_oid("agtype_access_operator", 1, - AGTYPEARRAYOID); + func_access_oid = get_agtype_access_operator_oid(); /* get the agtype_access_slice function */ - func_slice_oid = get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, - AGTYPEOID, AGTYPEOID); + func_slice_oid = get_agtype_access_slice_oid(); /* * If the indirection argument is a ColumnRef, we want to pull out the @@ -1551,18 +1573,17 @@ static Node *transform_cypher_string_match(cypher_parsestate *cpstate, FuncExpr *func_expr; Oid func_access_oid; List *args = NIL; - const char *func_name; switch (csm_node->operation) { case CSMO_STARTS_WITH: - func_name = "agtype_string_match_starts_with"; + func_access_oid = get_agtype_string_match_starts_with_oid(); break; case CSMO_ENDS_WITH: - func_name = "agtype_string_match_ends_with"; + func_access_oid = get_agtype_string_match_ends_with_oid(); break; case CSMO_CONTAINS: - func_name = "agtype_string_match_contains"; + func_access_oid = get_agtype_string_match_contains_oid(); break; default: @@ -1570,8 +1591,6 @@ static Node *transform_cypher_string_match(cypher_parsestate *cpstate, (errmsg_internal("unknown Cypher string match operation"))); } - func_access_oid = get_ag_func_oid(func_name, 2, AGTYPEOID, AGTYPEOID); - expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); args = lappend(args, expr); expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); @@ -1832,11 +1851,8 @@ static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, FuncExpr *fexpr) { - ParseState *pstate = &cpstate->pstate; - Node *last_srf = pstate->p_last_srf; - Node *retval = NULL; - List *fname = NIL; - FuncCall *fnode = NULL; + Oid func_oid; + FuncExpr *retval; if (fexpr->funcresulttype != TEXTOID) { @@ -1845,18 +1861,13 @@ static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, errmsg("can only wrap text to agtype"))); } - /* make a function call node to cast text to agtype */ - fname = list_make2(makeString("ag_catalog"), makeString("text_to_agtype")); - - /* the input function is the arg to the new function (wrapper) */ - fnode = makeFuncCall(fname, list_make1(fexpr), COERCE_SQL_SYNTAX, -1); - - /* ... and hand off to ParseFuncOrColumn to create it */ - retval = ParseFuncOrColumn(pstate, fname, list_make1(fexpr), last_srf, - fnode, false, -1); + func_oid = get_text_to_agtype_oid(); + retval = makeFuncExpr(func_oid, AGTYPEOID, list_make1(fexpr), InvalidOid, + InvalidOid, COERCE_SQL_SYNTAX); + retval->location = fexpr->location; /* return the wrapped function */ - return retval; + return (Node *)retval; } /* @@ -2176,6 +2187,204 @@ static void invalidate_function_caches(Datum arg, int cache_id, hash_destroy(function_extension_cache); function_extension_cache = NULL; } + + agtype_access_operator_oid = InvalidOid; + agtype_access_slice_oid = InvalidOid; + agtype_in_operator_oid = InvalidOid; + agtype_add_oid = InvalidOid; + agtype_build_empty_list_oid = InvalidOid; + agtype_build_list_oid = InvalidOid; + agtype_build_empty_map_oid = InvalidOid; + agtype_build_map_oid = InvalidOid; + agtype_build_map_nonull_oid = InvalidOid; + age_properties_oid = InvalidOid; + agtype_string_match_starts_with_oid = InvalidOid; + agtype_string_match_ends_with_oid = InvalidOid; + agtype_string_match_contains_oid = InvalidOid; + text_to_agtype_oid = InvalidOid; +} + +static Oid get_agtype_access_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_operator_oid)) + { + agtype_access_operator_oid = + get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); + } + + return agtype_access_operator_oid; +} + +static Oid get_agtype_access_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_slice_oid)) + { + agtype_access_slice_oid = + get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, AGTYPEOID, + AGTYPEOID); + } + + return agtype_access_slice_oid; +} + +static Oid get_agtype_in_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_in_operator_oid)) + { + agtype_in_operator_oid = + get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_in_operator_oid; +} + +static Oid get_agtype_add_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_add_oid)) + { + agtype_add_oid = + get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_add_oid; +} + +static Oid get_agtype_build_empty_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_list_oid)) + { + agtype_build_empty_list_oid = + get_ag_func_oid("agtype_build_list", 0); + } + + return agtype_build_empty_list_oid; +} + +static Oid get_agtype_build_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_list_oid)) + { + agtype_build_list_oid = + get_ag_func_oid("agtype_build_list", 1, ANYOID); + } + + return agtype_build_list_oid; +} + +static Oid get_agtype_build_empty_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_map_oid)) + { + agtype_build_empty_map_oid = get_ag_func_oid("agtype_build_map", 0); + } + + return agtype_build_empty_map_oid; +} + +static Oid get_agtype_build_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_oid)) + { + agtype_build_map_oid = + get_ag_func_oid("agtype_build_map", 1, ANYOID); + } + + return agtype_build_map_oid; +} + +static Oid get_agtype_build_map_nonull_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_nonull_oid)) + { + agtype_build_map_nonull_oid = + get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + } + + return agtype_build_map_nonull_oid; +} + +static Oid get_age_properties_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_properties_oid)) + { + age_properties_oid = get_ag_func_oid("age_properties", 1, AGTYPEOID); + } + + return age_properties_oid; +} + +static Oid get_agtype_string_match_starts_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_starts_with_oid)) + { + agtype_string_match_starts_with_oid = + get_ag_func_oid("agtype_string_match_starts_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_starts_with_oid; +} + +static Oid get_agtype_string_match_ends_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_ends_with_oid)) + { + agtype_string_match_ends_with_oid = + get_ag_func_oid("agtype_string_match_ends_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_ends_with_oid; +} + +static Oid get_agtype_string_match_contains_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_contains_oid)) + { + agtype_string_match_contains_oid = + get_ag_func_oid("agtype_string_match_contains", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_contains_oid; +} + +static Oid get_text_to_agtype_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(text_to_agtype_oid)) + { + text_to_agtype_oid = get_ag_func_oid("text_to_agtype", 1, TEXTOID); + } + + return text_to_agtype_oid; } /* diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 5b92e1588..09aa42f6f 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -175,10 +175,14 @@ static GRAPH_global_context_container global_graph_contexts_container = {0}; /* declarations */ /* GRAPH global context functions */ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx); +static GRAPH_global_context *manage_GRAPH_global_contexts_internal( + const char *graph_name, int graph_name_len, Oid graph_oid); static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid); -static bool delete_specific_GRAPH_global_contexts(char *graph_name); +static bool delete_specific_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len); static bool delete_GRAPH_global_contexts(void); -static Oid get_cached_global_graph_oid(const char *graph_name); +static Oid get_cached_global_graph_oid_len(const char *graph_name, + int graph_name_len); static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_vertex_hashtable(GRAPH_global_context *ggctx); @@ -908,6 +912,21 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) */ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, Oid graph_oid) +{ + return manage_GRAPH_global_contexts_internal(graph_name, strlen(graph_name), + graph_oid); +} + +GRAPH_global_context *manage_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len, + Oid graph_oid) +{ + return manage_GRAPH_global_contexts_internal(graph_name, graph_name_len, + graph_oid); +} + +static GRAPH_global_context *manage_GRAPH_global_contexts_internal( + const char *graph_name, int graph_name_len, Oid graph_oid) { GRAPH_global_context *new_ggctx = NULL; GRAPH_global_context *curr_ggctx = NULL; @@ -1012,7 +1031,7 @@ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, global_graph_contexts_container.contexts = new_ggctx; /* set the graph name and oid */ - new_ggctx->graph_name = pstrdup(graph_name); + new_ggctx->graph_name = pnstrdup(graph_name, graph_name_len); new_ggctx->graph_oid = graph_oid; /* set the graph version counter for cache invalidation */ @@ -1095,7 +1114,8 @@ static bool delete_GRAPH_global_contexts(void) * Helper function to delete a specific global graph context used by the * process. */ -static bool delete_specific_GRAPH_global_contexts(char *graph_name) +static bool delete_specific_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len) { Oid graph_oid = InvalidOid; @@ -1104,32 +1124,37 @@ static bool delete_specific_GRAPH_global_contexts(char *graph_name) return false; } - /* get the graph oid */ - graph_oid = get_cached_global_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid_len(graph_name, graph_name_len); return delete_specific_GRAPH_global_context_by_oid(graph_oid); } -static Oid get_cached_global_graph_oid(const char *graph_name) +static Oid get_cached_global_graph_oid_len(const char *graph_name, + int graph_name_len) { static NameData cached_graph_name; static Oid cached_graph_oid = InvalidOid; static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; if (OidIsValid(cached_graph_oid) && - namestrcmp(&cached_graph_name, graph_name) == 0 && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0' && cached_generation == current_generation) { return cached_graph_oid; } - cached_graph_oid = get_graph_oid(graph_name); + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name); + namestrcpy(&cached_graph_name, graph_name_cstr); cached_generation = get_graph_cache_generation(); } + pfree(graph_name_cstr); return cached_graph_oid; } @@ -1509,12 +1534,8 @@ Datum age_delete_global_graphs(PG_FUNCTION_ARGS) } else if (agtv_temp->type == AGTV_STRING) { - char *graph_name = NULL; - - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - - success = delete_specific_GRAPH_global_contexts(graph_name); + success = delete_specific_GRAPH_global_contexts_len( + agtv_temp->val.string.val, agtv_temp->val.string.len); } else { @@ -1538,7 +1559,6 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) agtype_value *agtv_temp = NULL; agtype_value agtv_integer; agtype_in_state result; - char *graph_name = NULL; Oid graph_oid = InvalidOid; graphid vid = 0; int64 self_loops = 0; @@ -1568,23 +1588,20 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) agtv_vertex = get_agtype_value("vertex_stats", AG_GET_ARG_AGTYPE_P(1), AGTV_VERTEX, true); - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - /* get the graph oid */ - graph_oid = get_cached_global_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid_len(agtv_temp->val.string.val, + agtv_temp->val.string.len); /* * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts(graph_name, graph_oid); - - /* free the graph name */ - pfree_if_not_null(graph_name); + ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, + agtv_temp->val.string.len, + graph_oid); /* get the id */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "id"); + agtv_temp = AGTYPE_VERTEX_GET_ID(agtv_vertex); vid = agtv_temp->val.int_value; /* get the vertex entry */ @@ -1602,7 +1619,7 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); /* store the label */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_vertex, "label"); + agtv_temp = AGTYPE_VERTEX_GET_LABEL(agtv_vertex); result.res = push_agtype_value(&result.parse_state, WAGT_KEY, string_to_agtype_value("label")); result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); @@ -1653,7 +1670,6 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) agtype_value *agtv_temp = NULL; agtype_value agtv_integer; agtype_in_state result; - char *graph_name = NULL; Oid graph_oid = InvalidOid; /* the graph name is required, but this generally isn't user supplied */ @@ -1668,11 +1684,9 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) agtv_temp = get_agtype_value("graph_stats", AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true); - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); - /* get the graph oid */ - graph_oid = get_cached_global_graph_oid(graph_name); + graph_oid = get_cached_global_graph_oid_len(agtv_temp->val.string.val, + agtv_temp->val.string.len); /* * Remove any context for this graph. This is done to allow graph_stats to @@ -1684,10 +1698,9 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts(graph_name, graph_oid); - - /* free the graph name */ - pfree_if_not_null(graph_name); + ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, + agtv_temp->val.string.len, + graph_oid); /* zero the state */ memset(&result, 0, sizeof(agtype_in_state)); diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index f9885df0d..34a778165 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -159,12 +159,16 @@ typedef struct VLE_path_container static VLE_local_context *global_vle_local_contexts = NULL; /* agtype functions */ +static agtype_value *get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, + int argno, + const char *type_error_msg); static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, HTAB *relation_cache); /* VLE local context functions */ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); -static Oid get_cached_vle_graph_oid(const char *graph_name); +static Oid get_cached_vle_graph_oid(const char *graph_name, + int graph_name_len); static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx); static void free_VLE_local_context(VLE_local_context *vlelctx); /* VLE graph traversal functions */ @@ -485,6 +489,47 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, } } +static agtype_value *get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, + int argno, + const char *type_error_msg) +{ + agtype *agt_arg; + agtype_value *agtv_value; + + if (PG_ARGISNULL(argno)) + { + return NULL; + } + + agt_arg = AG_GET_ARG_AGTYPE_P(argno); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle: agtype argument must be a scalar"))); + } + + agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + if (agtv_value->type == AGTV_NULL) + { + return NULL; + } + + if (agtv_value->type == AGTV_VERTEX) + { + agtv_value = AGTYPE_VERTEX_GET_ID(agtv_value); + } + else if (agtv_value->type != AGTV_INTEGER) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + return agtv_value; +} + /* * Helper function to free up the memory used by the VLE_local_context. * @@ -617,8 +662,11 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, use_cache = true; } - /* fetch the VLE_local_context if it is cached */ - vlelctx = get_cached_VLE_local_context(vle_grammar_node_id); + /* fetch the VLE_local_context only when this call can reuse one */ + if (use_cache) + { + vlelctx = get_cached_VLE_local_context(vle_grammar_node_id); + } /* if we are caching VLE_local_contexts and this grammar node is cached */ if (use_cache && vlelctx != NULL) @@ -631,7 +679,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, */ /* get and update the start vertex id */ - if (PG_ARGISNULL(1) || is_agtype_null(AG_GET_ARG_AGTYPE_P(1))) + agtv_temp = get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id"); + if (agtv_temp == NULL) { /* if there are no more vertices to process, return NULL */ if (vlelctx->next_vertex == NULL) @@ -644,40 +695,19 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start vertex argument must be a vertex or the integer id"))); - } vlelctx->vsid = agtv_temp->val.int_value; } /* get and update the end vertex id */ - if (PG_ARGISNULL(2) || is_agtype_null(AG_GET_ARG_AGTYPE_P(2))) + agtv_temp = get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id"); + if (agtv_temp == NULL) { vlelctx->veid = 0; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(2), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("end vertex argument must be a vertex or the integer id"))); - } vlelctx->veid = agtv_temp->val.int_value; } vlelctx->is_dirty = true; @@ -714,16 +744,20 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, /* get the graph name - this is a required argument */ agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true); + /* get the graph oid before copying the graph name into VLE state */ + graph_oid = get_cached_vle_graph_oid(agtv_temp->val.string.val, + agtv_temp->val.string.len); + graph_name = pnstrdup(agtv_temp->val.string.val, agtv_temp->val.string.len); - /* get the graph oid */ - graph_oid = get_cached_vle_graph_oid(graph_name); /* * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts(graph_name, graph_oid); + ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, + agtv_temp->val.string.len, + graph_oid); /* allocate and initialize local VLE context */ vlelctx = palloc0(sizeof(VLE_local_context)); @@ -757,7 +791,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, * which path function is used. If a start vertex isn't provided, we * retrieve them incrementally from the vertices list. */ - if (PG_ARGISNULL(1) || is_agtype_null(AG_GET_ARG_AGTYPE_P(1))) + agtv_temp = get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id"); + if (agtv_temp == NULL) { /* set _TO */ vlelctx->path_function = VLE_FUNCTION_PATHS_TO; @@ -769,18 +806,6 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start vertex argument must be a vertex or the integer id"))); - } vlelctx->vsid = agtv_temp->val.int_value; } @@ -788,7 +813,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, * Get the end vertex id - this is an optional parameter and determines * which path function is used. */ - if (PG_ARGISNULL(2) || is_agtype_null(AG_GET_ARG_AGTYPE_P(2))) + agtv_temp = get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id"); + if (agtv_temp == NULL) { if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) { @@ -802,18 +830,6 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(2), - AGTV_VERTEX, false); - if (agtv_temp != NULL && agtv_temp->type == AGTV_VERTEX) - { - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "id"); - } - else if (agtv_temp == NULL || agtv_temp->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("end vertex argument must be a vertex or the integer id"))); - } vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; vlelctx->veid = agtv_temp->val.int_value; } @@ -823,7 +839,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, AGTV_EDGE, true); /* get the edge prototype's property conditions */ - agtv_object = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "properties"); + agtv_object = AGTYPE_EDGE_GET_PROPERTIES(agtv_temp); agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); /* store the properties as an agtype */ @@ -836,7 +852,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, vlelctx->edge_property_constraint_hash = datum_image_hash(d_edge_property_constraint, false, -1); /* get the edge prototype's label name */ - agtv_temp = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "label"); + agtv_temp = AGTYPE_EDGE_GET_LABEL(agtv_temp); if (agtv_temp->type == AGTV_STRING && agtv_temp->val.string.len != 0) { @@ -916,26 +932,32 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, return vlelctx; } -static Oid get_cached_vle_graph_oid(const char *graph_name) +static Oid get_cached_vle_graph_oid(const char *graph_name, + int graph_name_len) { static NameData cached_graph_name; static Oid cached_graph_oid = InvalidOid; static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; if (OidIsValid(cached_graph_oid) && - namestrcmp(&cached_graph_name, graph_name) == 0 && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0' && cached_generation == current_generation) { return cached_graph_oid; } - cached_graph_oid = get_graph_oid(graph_name); + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name); + namestrcpy(&cached_graph_name, graph_name_cstr); cached_generation = get_graph_cache_generation(); } + pfree(graph_name_cstr); return cached_graph_oid; } @@ -1316,6 +1338,8 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, GraphIdNode *edge_out = NULL; GraphIdNode *edge_self = NULL; HTAB *relation_cache = NULL; + graphid source_vertex_id = 0; + int64 path_stack_size; /* get the vertex entry */ ve = get_vertex_entry(vlelctx->ggctx, vertex_id); @@ -1328,6 +1352,11 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, /* point to stacks */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + path_stack_size = gid_stack_size(vlelctx->dfs_path_stack); + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + source_vertex_id = get_vertex_entry_id(ve); + } /* set to the first edge for each edge list for the specified direction */ if (vlelctx->edge_direction == CYPHER_REL_DIR_RIGHT || @@ -1358,7 +1387,6 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, edge_entry *ee = NULL; edge_state_entry *ese = NULL; graphid edge_id; - int64 path_stack_size; /* get the edge_id from the next available edge*/ if (edge_out != NULL) @@ -1378,7 +1406,6 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, * This is a fast existence check, relative to the hash search, for when * the path stack is small. If the edge is in the path, we skip it. */ - path_stack_size = gid_stack_size(vlelctx->dfs_path_stack); if (path_stack_size < 10 && is_edge_in_path(vlelctx, edge_id, path_stack_size)) { @@ -1438,7 +1465,7 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, */ if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) { - gid_stack_push(vertex_stack, get_vertex_entry_id(ve)); + gid_stack_push(vertex_stack, source_vertex_id); } gid_stack_push(edge_stack, edge_id); } @@ -1640,7 +1667,7 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state edges_result; - HTAB *relation_cache; + HTAB *relation_cache = NULL; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; @@ -1662,8 +1689,11 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) MemSet(&edges_result, 0, sizeof(agtype_in_state)); edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_BEGIN_ARRAY, NULL); - relation_cache = create_entry_property_relation_cache( - "vle edge materialization relation cache"); + if (graphid_array_size > 2) + { + relation_cache = create_entry_property_relation_cache( + "vle edge materialization relation cache"); + } for (index = 1; index < graphid_array_size - 1; index += 2) { diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 16656201e..be82423b8 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -114,6 +114,7 @@ typedef enum /* type categories for datum_to_agtype */ static inline Datum agtype_from_cstring(char *str, int len); size_t check_string_length(size_t len); +static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno); static void agtype_in_agtype_annotation(void *pstate, char *annotation); static void agtype_in_object_start(void *pstate); static void agtype_in_object_end(void *pstate); @@ -178,15 +179,16 @@ static bool is_object_vertex(agtype_value *agtv); static bool is_object_edge(agtype_value *agtv); static bool is_array_path(agtype_value *agtv); /* graph entity retrieval */ -static Oid get_cached_graph_oid_for_name(const char *graph_name); +static Oid get_cached_graph_oid_for_name(const char *graph_name, + int graph_name_len); static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, int64 graphid); static void initialize_btree_index_attr_cache(void); static void invalidate_btree_index_attr_cache(Datum arg, Oid relid); static Oid find_usable_btree_index_for_attr_uncached(Relation rel, AttrNumber attnum); -static char *get_label_name(const char *graph_name, graphid element_graphid, - Oid *label_relation); +static char *get_label_name(const char *graph_name, int graph_name_len, + graphid element_graphid, Oid *label_relation); static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, bool *is_null); static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, @@ -498,6 +500,21 @@ static inline Datum agtype_from_cstring(char *str, int len) PG_RETURN_POINTER(agt); } +static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno) +{ + Oid *cached_type; + + if (fcinfo->flinfo->fn_extra == NULL) + { + cached_type = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + sizeof(Oid)); + *cached_type = get_fn_expr_argtype(fcinfo->flinfo, argno); + fcinfo->flinfo->fn_extra = cached_type; + } + + return *(Oid *)fcinfo->flinfo->fn_extra; +} + size_t check_string_length(size_t len) { if (len > AGTENTRY_OFFLENMASK) @@ -3449,7 +3466,7 @@ Datum agtype_to_int4_array(PG_FUNCTION_ARGS) int i; /* get the input data type */ - arg_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + arg_type = get_cached_fn_expr_argtype(fcinfo, 0); /* verify the input is agtype */ if (arg_type != AGTYPEOID) @@ -5683,10 +5700,10 @@ Datum age_end_id(PG_FUNCTION_ARGS) * node or edge. The returned pointer is owned by the label cache and must not * be modified or freed. */ -static char *get_label_name(const char *graph_name, graphid element_graphid, - Oid *label_relation) +static char *get_label_name(const char *graph_name, int graph_name_len, + graphid element_graphid, Oid *label_relation) { - Oid graph_oid = get_cached_graph_oid_for_name(graph_name); + Oid graph_oid = get_cached_graph_oid_for_name(graph_name, graph_name_len); int32 label_id = get_graphid_label_id(element_graphid); label_cache_data *label_cache; @@ -5704,26 +5721,32 @@ static char *get_label_name(const char *graph_name, graphid element_graphid, return NameStr(label_cache->name); } -static Oid get_cached_graph_oid_for_name(const char *graph_name) +static Oid get_cached_graph_oid_for_name(const char *graph_name, + int graph_name_len) { static NameData cached_graph_name; static Oid cached_graph_oid = InvalidOid; static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); + char *graph_name_cstr; if (OidIsValid(cached_graph_oid) && - namestrcmp(&cached_graph_name, graph_name) == 0 && + graph_name_len < NAMEDATALEN && + strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && + NameStr(cached_graph_name)[graph_name_len] == '\0' && cached_generation == current_generation) { return cached_graph_oid; } - cached_graph_oid = get_graph_oid(graph_name); + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name); + namestrcpy(&cached_graph_name, graph_name_cstr); cached_generation = get_graph_cache_generation(); } + pfree(graph_name_cstr); return cached_graph_oid; } @@ -5889,7 +5912,8 @@ Datum age_startnode(PG_FUNCTION_ARGS) agtype *agt_arg = NULL; agtype_value *agtv_object = NULL; agtype_value *agtv_value = NULL; - char *graph_name = NULL; + char *graph_name; + int graph_name_len; char *label_name = NULL; Oid label_relation = InvalidOid; graphid start_id; @@ -5908,8 +5932,8 @@ Datum age_startnode(PG_FUNCTION_ARGS) Assert(AGT_ROOT_IS_SCALAR(agt_arg)); agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); Assert(agtv_object->type == AGTV_STRING); - graph_name = pnstrdup(agtv_object->val.string.val, - agtv_object->val.string.len); + graph_name = agtv_object->val.string.val; + graph_name_len = agtv_object->val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5929,15 +5953,19 @@ Datum age_startnode(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("startNode() argument must be an edge or null"))); - /* get the graphid for start_id */ - agtv_value = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "start_id"); + /* + * Get the graphid for start_id. Edge objects have deterministic field + * ordering, so use direct access instead of a keyed lookup. + */ + agtv_value = AGTYPE_EDGE_GET_START_ID(agtv_object); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_INTEGER); + Assert(agtv_value->type == AGTV_INTEGER); start_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, start_id, &label_relation); + label_name = get_label_name(graph_name, graph_name_len, start_id, + &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); Assert(OidIsValid(label_relation)); @@ -5954,7 +5982,8 @@ Datum age_endnode(PG_FUNCTION_ARGS) agtype *agt_arg = NULL; agtype_value *agtv_object = NULL; agtype_value *agtv_value = NULL; - char *graph_name = NULL; + char *graph_name; + int graph_name_len; char *label_name = NULL; Oid label_relation = InvalidOid; graphid end_id; @@ -5973,8 +6002,8 @@ Datum age_endnode(PG_FUNCTION_ARGS) Assert(AGT_ROOT_IS_SCALAR(agt_arg)); agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); Assert(agtv_object->type == AGTV_STRING); - graph_name = pnstrdup(agtv_object->val.string.val, - agtv_object->val.string.len); + graph_name = agtv_object->val.string.val; + graph_name_len = agtv_object->val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -5994,15 +6023,19 @@ Datum age_endnode(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("endNode() argument must be an edge or null"))); - /* get the graphid for the end_id */ - agtv_value = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "end_id"); + /* + * Get the graphid for end_id. Edge objects have deterministic field + * ordering, so use direct access instead of a keyed lookup. + */ + agtv_value = AGTYPE_EDGE_GET_END_ID(agtv_object); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_INTEGER); + Assert(agtv_value->type == AGTV_INTEGER); end_id = agtv_value->val.int_value; /* get the label */ - label_name = get_label_name(graph_name, end_id, &label_relation); + label_name = get_label_name(graph_name, graph_name_len, end_id, + &label_relation); /* it must not be null and must be a string */ Assert(label_name != NULL); Assert(OidIsValid(label_relation)); @@ -6161,7 +6194,7 @@ Datum age_tail(PG_FUNCTION_ARGS) } /* get the data type */ - arg_type = get_fn_expr_argtype(fcinfo->flinfo, 0); + arg_type = get_cached_fn_expr_argtype(fcinfo, 0); /* check the data type */ if (arg_type != AGTYPEOID) @@ -7292,10 +7325,10 @@ Datum age_type(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("type() argument must be an edge or null"))); - agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_object, "label"); + agtv_result = AGTYPE_EDGE_GET_LABEL(agtv_object); Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_STRING); + Assert(agtv_result->type == AGTV_STRING); PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } @@ -7489,7 +7522,7 @@ Datum age_tostring(PG_FUNCTION_ARGS) /* get the argument and type */ arg = PG_GETARG_DATUM(0); - type = get_fn_expr_argtype(fcinfo->flinfo, 0); + type = get_cached_fn_expr_argtype(fcinfo, 0); /* verify that if the type is UNKNOWNOID it can be converted */ if (type == UNKNOWNOID && !get_fn_expr_arg_stable(fcinfo->flinfo, 0)) @@ -7929,7 +7962,7 @@ Datum age_reverse(PG_FUNCTION_ARGS) agtv_value = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); Assert(agtv_value != NULL); - Assert(agtv_value->type = AGTV_ARRAY); + Assert(agtv_value->type == AGTV_ARRAY); PG_RETURN_POINTER(agtype_value_to_agtype(agtv_value)); @@ -11937,11 +11970,17 @@ Datum age_keys(PG_FUNCTION_ARGS) if (agtv_result->type == AGTV_EDGE || agtv_result->type == AGTV_VERTEX) { - agtv_result = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_result, - "properties"); + if (agtv_result->type == AGTV_VERTEX) + { + agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_result); + } + else + { + agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_result); + } Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_OBJECT); + Assert(agtv_result->type == AGTV_OBJECT); } else { @@ -11971,7 +12010,7 @@ Datum age_keys(PG_FUNCTION_ARGS) agtv_result = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); Assert(agtv_result != NULL); - Assert(agtv_result->type = AGTV_ARRAY); + Assert(agtv_result->type == AGTV_ARRAY); PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } @@ -12083,7 +12122,7 @@ Datum age_labels(PG_FUNCTION_ARGS) } /* get the label from the vertex */ - agtv_label = GET_AGTYPE_VALUE_OBJECT_VALUE(agtv_temp, "label"); + agtv_label = AGTYPE_VERTEX_GET_LABEL(agtv_temp); /* it cannot be NULL */ Assert(agtv_label != NULL); @@ -12605,7 +12644,7 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) } /* get the type of the input argument */ - type = get_fn_expr_argtype(fcinfo->flinfo, 0); + type = get_cached_fn_expr_argtype(fcinfo, 0); /* if it is NOT an AGTYPE, we need convert it to one, if possible */ if (type != AGTYPEOID) diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 5379b37f4..7631223d4 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -617,11 +617,11 @@ static void initialize_label_caches(void) ag_cache_scan_key_init(&label_name_graph_scan_keys[0], Anum_ag_label_name, F_NAMEEQ); ag_cache_scan_key_init(&label_name_graph_scan_keys[1], Anum_ag_label_graph, - F_INT4EQ); + F_OIDEQ); /* ag_label.graph, ag_label.id */ ag_cache_scan_key_init(&label_graph_oid_scan_keys[0], Anum_ag_label_graph, - F_INT4EQ); + F_OIDEQ); ag_cache_scan_key_init(&label_graph_oid_scan_keys[1], Anum_ag_label_id, F_INT4EQ); diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index e8223f5fb..0b6d24f32 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -123,6 +123,9 @@ typedef struct cypher_merge_custom_scan_state bool eager_buffer_filled; cypher_update_information *on_match_set_info; /* NULL if not specified */ cypher_update_information *on_create_set_info; /* NULL if not specified */ + HTAB *update_qual_cache; + HTAB *update_index_cache; + HTAB *update_result_rel_info_cache; HTAB *entity_exists_index_cache; HTAB *result_rel_info_cache; } cypher_merge_custom_scan_state; @@ -147,6 +150,7 @@ void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache); HTAB *create_entity_exists_index_cache(const char *name); void destroy_entity_exists_index_cache(HTAB *index_cache); +void destroy_index_cache(HTAB *index_cache, bool close_relations); bool entity_exists(EState *estate, Oid graph_oid, graphid id); bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, HTAB *index_cache); @@ -189,6 +193,8 @@ typedef struct IndexCacheEntry { Oid start_index_oid; bool end_index_oid_cached; Oid end_index_oid; + TupleTableSlot *slot; + TupleTableSlot *update_slot; } IndexCacheEntry; static inline void init_index_cache_entry(IndexCacheEntry *entry) @@ -200,6 +206,8 @@ static inline void init_index_cache_entry(IndexCacheEntry *entry) entry->start_index_oid = InvalidOid; entry->end_index_oid_cached = false; entry->end_index_oid = InvalidOid; + entry->slot = NULL; + entry->update_slot = NULL; } #endif diff --git a/src/include/utils/age_global_graph.h b/src/include/utils/age_global_graph.h index 85b698649..2a1cf3582 100644 --- a/src/include/utils/age_global_graph.h +++ b/src/include/utils/age_global_graph.h @@ -40,6 +40,9 @@ typedef struct GRAPH_global_context GRAPH_global_context; /* GRAPH global context functions */ GRAPH_global_context *manage_GRAPH_global_contexts(char *graph_name, Oid graph_oid); +GRAPH_global_context *manage_GRAPH_global_contexts_len(const char *graph_name, + int graph_name_len, + Oid graph_oid); GRAPH_global_context *find_GRAPH_global_context(Oid graph_oid); bool is_ggctx_invalid(GRAPH_global_context *ggctx); /* GRAPH retrieval functions */ diff --git a/src/include/utils/load/age_load.h b/src/include/utils/load/age_load.h index 2eec6fbf5..3bd5b46ce 100644 --- a/src/include/utils/load/age_load.h +++ b/src/include/utils/load/age_load.h @@ -31,8 +31,13 @@ #include "commands/graph_commands.h" #include "utils/ag_cache.h" -#define BATCH_SIZE 1000 -#define MAX_BUFFERED_BYTES 65535 /* 64KB, same as pg COPY */ +/* + * Keep enough rows buffered for heap_multi_insert() to amortize executor, + * index, and command-counter overhead during CSV loads. The byte cap prevents + * very wide property maps from accumulating unbounded memory. + */ +#define BATCH_SIZE 4096 +#define MAX_BUFFERED_BYTES (4 * 1024 * 1024) typedef struct batch_insert_state { From 357550f4e148c8ade93476ef82061111b86d65cb Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:38 +0900 Subject: [PATCH 07/15] Fast path agtype argument handling Add fast paths for scalar, variadic, string, math, range, collect, access, and small agtype builder arguments. The grouped change caches argument types, avoids fallback conversion when the input shape is already known, reduces startup allocations in lookup caches, uses direct search-path membership checks, and trims zero-fill work in agtype, VLE, MERGE, parser, aggregate, and graph traversal setup paths. --- src/backend/executor/cypher_delete.c | 26 +- src/backend/executor/cypher_merge.c | 9 +- src/backend/executor/cypher_set.c | 56 +- src/backend/executor/cypher_utils.c | 36 +- src/backend/parser/cypher_clause.c | 12 +- src/backend/parser/cypher_expr.c | 75 +- src/backend/parser/cypher_gram.y | 16 +- src/backend/parser/cypher_item.c | 4 +- src/backend/parser/cypher_parse_node.c | 2 +- src/backend/utils/adt/age_global_graph.c | 70 +- src/backend/utils/adt/age_graphid_ds.c | 8 +- src/backend/utils/adt/age_vle.c | 162 +++- src/backend/utils/adt/agtype.c | 940 ++++++++++++++--------- src/backend/utils/adt/agtype_ops.c | 63 +- src/backend/utils/adt/agtype_parser.c | 11 +- src/backend/utils/adt/agtype_raw.c | 2 +- src/backend/utils/adt/agtype_util.c | 6 +- src/backend/utils/ag_func.c | 46 +- src/backend/utils/cache/ag_cache.c | 13 +- src/include/executor/cypher_utils.h | 2 +- 20 files changed, 983 insertions(+), 576 deletions(-) diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 23b652bac..87a23e71f 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -109,8 +109,8 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, sizeof(graphid); /* entries are not used, but entrysize must >= keysize */ hashctl.hash = tag_hash; css->vertex_id_htab = hash_create(DELETE_VERTEX_HTAB_NAME, - DELETE_VERTEX_HTAB_SIZE, &hashctl, - HASH_ELEM | HASH_FUNCTION); + DELETE_VERTEX_HTAB_INITIAL_SIZE, + &hashctl, HASH_ELEM | HASH_FUNCTION); init_delete_caches(css); /* @@ -337,6 +337,7 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, TM_Result lock_result; TM_Result delete_result; Buffer buffer; + CommandId current_cid; /* Find the physical tuple, this variable is coming from */ saved_resultRels = estate->es_result_relations; @@ -395,8 +396,9 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, CommandCounterIncrement(); /* Update command id in estate */ - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + current_cid = GetCurrentCommandId(false); + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; } else if (lock_result != TM_Invisible && lock_result != TM_SelfModified) { @@ -545,8 +547,12 @@ static void process_delete_list(CustomScanState *node) /* * Setup the scan description, with the correct snapshot and scan keys. */ - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + { + CommandId current_cid = GetCurrentCommandId(false); + + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; + } if (OidIsValid(index_oid)) { @@ -836,8 +842,12 @@ static void check_for_connected_edges(CustomScanState *node) label_info->relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); - estate->es_snapshot->curcid = GetCurrentCommandId(false); - estate->es_output_cid = GetCurrentCommandId(false); + { + CommandId current_cid = GetCurrentCommandId(false); + + estate->es_snapshot->curcid = current_cid; + estate->es_output_cid = current_cid; + } idx_entry = hash_search(css->index_cache, &relid, HASH_ENTER, &found_idx_entry); diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 2bd0d1175..aea995d58 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -448,14 +448,14 @@ static path_entry **prebuild_path(CustomScanState *node) int counter = 0; path_entry **path_array = NULL; - path_array = palloc0(sizeof(path_entry *) * css->path_length); + path_array = palloc(sizeof(path_entry *) * css->path_length); /* iterate through the path, partially prebuilding it */ foreach (lc, nodes) { /* get the node/edge and allocate the memory needed */ cypher_target_node *node = lfirst(lc); - path_entry *entry = palloc0(sizeof(path_entry)); + path_entry *entry = palloc(sizeof(path_entry)); /* if this isn't an actual passed in tuple */ if (CYPHER_TARGET_NODE_INSERT_ENTITY(node->flags)) @@ -734,8 +734,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) } else { - created_path *new_path = - palloc0(sizeof(created_path)); + created_path *new_path = palloc(sizeof(created_path)); new_path->next = css->created_paths_list; new_path->entry = prebuilt_path_array; @@ -837,7 +836,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) } else { - created_path *new_path = palloc0(sizeof(created_path)); + created_path *new_path = palloc(sizeof(created_path)); new_path->next = css->created_paths_list; new_path->entry = prebuilt_path_array; diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 81396162a..22d78e490 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -69,6 +69,7 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; Plan *subplan; + ListCell *lc; Assert(list_length(css->cs->custom_plans) == 1); css->graph_oid = css->set_list->graph_oid; @@ -108,6 +109,17 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + foreach(lc, css->set_list->set_items) + { + cypher_update_item *item = (cypher_update_item *)lfirst(lc); + + if (item->prop_expr != NULL) + { + item->prop_expr_state = ExecInitExpr((Expr *)item->prop_expr, + (PlanState *)node); + } + } + init_update_caches(&css->qual_cache, &css->index_cache, &css->result_rel_info_cache, "set_qual_cache", "set_index_cache", @@ -464,6 +476,7 @@ void apply_update_list(CustomScanState *node, EState *estate = node->ss.ps.state; int *luindex = NULL; int lidx = 0; + int num_set_items; HTAB *qual_cache = NULL; HTAB *index_cache = NULL; HTAB *result_rel_info_cache = NULL; @@ -523,8 +536,7 @@ void apply_update_list(CustomScanState *node, } } - /* allocate an array to hold the last update index of each 'entity' */ - luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid); + num_set_items = list_length(set_info->set_items); /* * Iterate through the SET items list and store the loop index of each @@ -535,15 +547,21 @@ void apply_update_list(CustomScanState *node, * to correctly update an 'entity' after all other previous updates to that * 'entity' have been done. */ - foreach (lc, set_info->set_items) + if (num_set_items > 1) { - cypher_update_item *update_item = NULL; + /* allocate an array to hold the last update index of each 'entity' */ + luindex = palloc(sizeof(int) * scanTupleSlot->tts_nvalid); - update_item = (cypher_update_item *)lfirst(lc); - luindex[update_item->entity_position - 1] = lidx; + foreach (lc, set_info->set_items) + { + cypher_update_item *update_item = NULL; - /* increment the loop index */ - lidx++; + update_item = (cypher_update_item *)lfirst(lc); + luindex[update_item->entity_position - 1] = lidx; + + /* increment the loop index */ + lidx++; + } } /* reset loop index */ @@ -571,7 +589,6 @@ void apply_update_list(CustomScanState *node, Datum new_entity; HeapTuple heap_tuple; char *clause_name = set_info->clause_name; - int cid; Oid index_oid = InvalidOid; Relation rel = NULL; Oid relid = InvalidOid; @@ -660,9 +677,8 @@ void apply_update_list(CustomScanState *node, bool isnull; /* - * Use the pre-initialized ExprState if available (set during - * plan init in begin_cypher_merge). Fall back to per-row init - * for callers that haven't pre-initialized (e.g. plain SET). + * Use the pre-initialized ExprState if available. Fall back to + * per-row init for callers that haven't pre-initialized. */ if (update_item->prop_expr_state != NULL) { @@ -701,7 +717,7 @@ void apply_update_list(CustomScanState *node, /* Alter the properties Agtype value. */ if (update_item->prop_name != NULL && - strcmp(update_item->prop_name, "") != 0) + update_item->prop_name[0] != '\0') { altered_properties = alter_property_value(original_properties, update_item->prop_name, @@ -771,11 +787,14 @@ void apply_update_list(CustomScanState *node, * If the last update index for the entity is equal to the current loop * index, then update this tuple. */ - cid = estate->es_snapshot->curcid; - estate->es_snapshot->curcid = GetCurrentCommandId(false); - - if (luindex[update_item->entity_position - 1] == lidx) + if (num_set_items == 1 || + luindex[update_item->entity_position - 1] == lidx) { + int cid; + + cid = estate->es_snapshot->curcid; + estate->es_snapshot->curcid = GetCurrentCommandId(false); + resultRelInfo = get_entity_result_rel_info( estate, result_rel_info_cache, label_cache->relation); @@ -967,10 +986,9 @@ void apply_update_list(CustomScanState *node, table_endscan(scan_desc); } + estate->es_snapshot->curcid = cid; } - estate->es_snapshot->curcid = cid; - /* increment loop index */ lidx++; } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index b2255ad22..e6510874d 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -257,6 +257,7 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, TupleTableSlot *slot; Oid index_oid = InvalidOid; CommandId saved_curcid; + CommandId current_cid; IndexCacheEntry *cache_entry = NULL; /* @@ -283,8 +284,8 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, * are visible at the current curcid become invisible. */ saved_curcid = estate->es_snapshot->curcid; - estate->es_snapshot->curcid = Max(saved_curcid, - GetCurrentCommandId(false)); + current_cid = GetCurrentCommandId(false); + estate->es_snapshot->curcid = Max(saved_curcid, current_cid); if (index_cache != NULL) { @@ -1108,13 +1109,13 @@ check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd) List *permissive_policies; List *restrictive_policies; List *securityQuals = NIL; - ListCell *lc; Oid user_id; bool hasSubLinks = false; bool result = true; EState *estate; ExprContext *econtext; TupleTableSlot *slot; + ExprState *qualExpr; /* If RLS is not enabled, tuple passes */ if (check_enable_rls(RelationGetRelid(rel), InvalidOid, true) != RLS_ENABLED) @@ -1153,32 +1154,9 @@ check_rls_for_tuple(Relation rel, HeapTuple tuple, CmdType cmd) ExecStoreHeapTuple(tuple, slot, false); econtext->ecxt_scantuple = slot; - /* Compile and evaluate each qual */ - foreach(lc, securityQuals) - { - Expr *qual = (Expr *) lfirst(lc); - ExprState *qualExpr; - List *qualList; - - /* ExecPrepareQual expects a List */ - if (!IsA(qual, List)) - { - qualList = list_make1(qual); - } - else - { - qualList = (List *) qual; - } - - /* Use ExecPrepareQual for standalone expression evaluation */ - qualExpr = ExecPrepareQual(qualList, estate); - - if (!ExecQual(qualExpr, econtext)) - { - result = false; - break; - } - } + /* Compile the qual list once and evaluate it as an implicit AND. */ + qualExpr = ExecPrepareQual(securityQuals, estate); + result = ExecQual(qualExpr, econtext); /* Clean up */ ExecDropSingleTupleTableSlot(slot); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 20e691dc3..43b96fdd2 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -5175,7 +5175,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ if (prop_var != NULL && pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, - strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) + sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) { prop_expr = prop_var; } @@ -5288,7 +5288,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ if (prop_var != NULL && pg_strncasecmp(rel->name, AGE_DEFAULT_ALIAS_PREFIX, - strlen(AGE_DEFAULT_ALIAS_PREFIX)) == 0) + sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) { prop_expr = prop_var; } @@ -6071,7 +6071,7 @@ static Expr *transform_cypher_node(cypher_parsestate *cpstate, */ is_vle = (strncmp(node->name, AGE_DEFAULT_PREFIX"vle_function_end_var", - strlen(AGE_DEFAULT_PREFIX"vle_function_end_var")) + sizeof(AGE_DEFAULT_PREFIX"vle_function_end_var") - 1) == 0); /* @@ -7259,8 +7259,10 @@ Query *cypher_parse_sub_analyze(Node *parseTree, pstate->p_locked_from_parent = locked_from_parent; pstate->p_resolve_unknowns = resolve_unknowns; - clause = palloc0(sizeof(cypher_clause)); + clause = palloc(sizeof(cypher_clause)); + clause->next = NULL; clause->self = parseTree; + clause->prev = NULL; query = transform_cypher_clause(cpstate, clause); free_parsestate(pstate); @@ -7653,7 +7655,7 @@ transform_merge_make_lateral_join(cypher_parsestate *cpstate, Query *query, { if (qte->resname != NULL && pg_strncasecmp(qte->resname, AGE_DEFAULT_VARNAME_PREFIX, - strlen(AGE_DEFAULT_VARNAME_PREFIX))) + sizeof(AGE_DEFAULT_VARNAME_PREFIX) - 1)) { qte->expr = add_volatile_wrapper(qte->expr); } diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 482ae74ae..e36e13f65 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -151,6 +151,7 @@ static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, FuncExpr *fexpr); static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); static char *get_mapped_extension(Oid func_oid); +static bool function_belongs_to_extension(Oid func_oid, const char *extension); static bool is_extension_external(char *extension); static char *construct_age_function_name(char *funcname); static void initialize_function_caches(void); @@ -631,6 +632,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) List *rvars; List *rnonvars; bool useOr; + bool is_not_in; ListCell *l; if (!is_ag_node(a->rexpr, cypher_list)) @@ -660,6 +662,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) Assert(is_ag_node(a->rexpr, cypher_list)); rexpr = (cypher_list *)a->rexpr; + is_not_in = (strcmp(strVal(linitial(a->name)), "<>") == 0); /* * Handle empty list case: x IN [] is always false, x NOT IN [] is always true. @@ -672,7 +675,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) Const *const_result; /* If operator is <> (NOT IN), result is true; otherwise (IN) result is false */ - if (strcmp(strVal(linitial(a->name)), "<>") == 0) + if (is_not_in) { bool_value = BoolGetDatum(true); } @@ -688,14 +691,7 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) } /* If the operator is <>, combine with AND not OR. */ - if (strcmp(strVal(linitial(a->name)), "<>") == 0) - { - useOr = false; - } - else - { - useOr = true; - } + useOr = !is_not_in; lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); @@ -1900,7 +1896,6 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) /* iterate through them and verify that they are in the search path */ for (i = 0; i < catlist->n_members; i++) { - ListCell *nsp; HeapTuple proctup = &catlist->members[i]->tuple; procform = (Form_pg_proc) GETSTRUCT(proctup); @@ -1913,16 +1908,10 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) nargs == procform->pronargs && fn->func_variadic == procform->provariadic) { - foreach(nsp, asp) + if (list_member_oid(asp, procform->pronamespace) && + !isTempNamespace(procform->pronamespace)) { - Oid oid = lfirst_oid(nsp); - - if (procform->pronamespace == oid && - isTempNamespace(procform->pronamespace) == false) - { - found = true; - break; - } + found = true; } } @@ -1996,6 +1985,45 @@ static char *get_mapped_extension(Oid func_oid) return extension; } +static bool function_belongs_to_extension(Oid func_oid, const char *extension) +{ + Oid extension_oid; + char *extension_name = NULL; + function_extension_cache_entry *entry; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->has_extension && + pg_strcasecmp(NameStr(entry->extension), extension) == 0; + } + + extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); + extension_name = get_extension_name(extension_oid); + + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension_name != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension_name); + } + } + + return extension_name != NULL && + pg_strcasecmp(extension_name, extension) == 0; +} + static bool is_extension_external(char *extension) { return ((extension != NULL) && @@ -2010,7 +2038,7 @@ static char *construct_age_function_name(char *funcname) int i; /* copy in the prefix - all AGE functions are prefixed with age_ */ - strncpy(ag_name, "age_", 4); + memcpy(ag_name, "age_", 4); /* * All AGE function names are in lower case. So, copy in the funcname @@ -2087,9 +2115,8 @@ static bool function_exists(char *funcname, char *extension) { HeapTuple proctup = &catlist->members[i]->tuple; Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); - char *ext = get_mapped_extension(procform->oid); - if (ext != NULL && pg_strcasecmp(ext, extension) == 0) + if (function_belongs_to_extension(procform->oid, extension)) { found = true; break; @@ -2129,7 +2156,7 @@ static void initialize_function_exists_cache(void) hash_ctl.entrysize = sizeof(function_exists_cache_entry); hash_ctl.hcxt = CacheMemoryContext; - function_exists_cache = hash_create("cypher function existence cache", 64, + function_exists_cache = hash_create("cypher function existence cache", 16, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } @@ -2151,7 +2178,7 @@ static void initialize_function_extension_cache(void) hash_ctl.hcxt = CacheMemoryContext; function_extension_cache = - hash_create("cypher function extension cache", 64, &hash_ctl, + hash_create("cypher function extension cache", 16, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); } diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index c614e1dbe..52a57b70d 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -3012,7 +3012,7 @@ static FuncCall *node_to_agtype(Node * fnode, char *type, int location) static char *create_unique_name(char *prefix_name) { char *name = NULL; - char *prefix = NULL; + char *prefix; uint nlen = 0; unsigned long unique_number = 0; @@ -3022,8 +3022,7 @@ static char *create_unique_name(char *prefix_name) /* was a valid prefix supplied */ if (prefix_name == NULL || strlen(prefix_name) <= 0) { - prefix = pnstrdup(UNIQUE_NAME_NULL_PREFIX, - strlen(UNIQUE_NAME_NULL_PREFIX)); + prefix = UNIQUE_NAME_NULL_PREFIX; } else { @@ -3034,24 +3033,19 @@ static char *create_unique_name(char *prefix_name) nlen = snprintf(NULL, 0, "%s_%lu", prefix, unique_number); /* allocate the space */ - name = palloc0(nlen + 1); + name = palloc(nlen + 1); /* create the name */ snprintf(name, nlen + 1, "%s_%lu", prefix, unique_number); - /* if we created the prefix, we need to free it */ - if (prefix_name == NULL || strlen(prefix_name) <= 0) - { - pfree_if_not_null(prefix); - } - return name; } /* function to check if given string has internal alias as prefix */ static bool has_internal_default_prefix(char *str) { - return strncmp(AGE_DEFAULT_PREFIX, str, strlen(AGE_DEFAULT_PREFIX)) == 0; + return strncmp(AGE_DEFAULT_PREFIX, str, + sizeof(AGE_DEFAULT_PREFIX) - 1) == 0; } /* function to return a unique unsigned long number */ diff --git a/src/backend/parser/cypher_item.c b/src/backend/parser/cypher_item.c index c2feb2720..a229833b2 100644 --- a/src/backend/parser/cypher_item.c +++ b/src/backend/parser/cypher_item.c @@ -181,8 +181,8 @@ static List *expand_pnsi_attrs(ParseState *pstate, ParseNamespaceItem *pnsi, List *names, *vars; ListCell *name, *var; List *te_list = NIL; - int var_prefix_len = strlen(AGE_DEFAULT_VARNAME_PREFIX); - int alias_prefix_len = strlen(AGE_DEFAULT_ALIAS_PREFIX); + int var_prefix_len = sizeof(AGE_DEFAULT_VARNAME_PREFIX) - 1; + int alias_prefix_len = sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1; vars = expandNSItemVars(pstate, pnsi, sublevels_up, location, &names); diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index 48c50d4fd..ae2570f93 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -132,7 +132,7 @@ char *get_next_default_alias(cypher_parsestate *cpstate) cpstate->default_alias_num); /* allocate the space */ - alias_name = palloc0(nlen + 1); + alias_name = palloc(nlen + 1); /* create the name */ snprintf(alias_name, nlen + 1, "%s%d", AGE_DEFAULT_ALIAS_PREFIX, diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 09aa42f6f..d6728f4fc 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -50,8 +50,8 @@ /* defines */ #define VERTEX_HTAB_NAME "Vertex to edge lists " /* added a space at end for */ #define EDGE_HTAB_NAME "Edge to vertex mapping " /* the graph name to follow */ -#define VERTEX_HTAB_INITIAL_SIZE 10000 -#define EDGE_HTAB_INITIAL_SIZE 10000 +#define VERTEX_HTAB_INITIAL_SIZE 1024 +#define EDGE_HTAB_INITIAL_SIZE 1024 /* Maximum number of graphs tracked for version counting */ #define AGE_MAX_GRAPHS 128 @@ -134,7 +134,7 @@ typedef struct edge_entry typedef struct graph_label_entry { - char *name; + NameData name; Oid relation; } graph_label_entry; @@ -265,26 +265,11 @@ static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx) char *graph_name = NULL; char *vhn = NULL; char *ehn = NULL; - int glen; - int vlen; - int elen; - /* get the graph name and length */ + /* get the graph name */ graph_name = ggctx->graph_name; - glen = strlen(graph_name); - /* get the vertex htab name length */ - vlen = strlen(VERTEX_HTAB_NAME); - /* get the edge htab name length */ - elen = strlen(EDGE_HTAB_NAME); - /* allocate the space and build the names */ - vhn = palloc0(vlen + glen + 1); - ehn = palloc0(elen + glen + 1); - /* copy in the names */ - strcpy(vhn, VERTEX_HTAB_NAME); - strcpy(ehn, EDGE_HTAB_NAME); - /* add in the graph name */ - vhn = strncat(vhn, graph_name, glen); - ehn = strncat(ehn, graph_name, glen); + vhn = psprintf("%s%s", VERTEX_HTAB_NAME, graph_name); + ehn = psprintf("%s%s", EDGE_HTAB_NAME, graph_name); /* initialize the vertex hashtable */ MemSet(&vertex_ctl, 0, sizeof(vertex_ctl)); @@ -349,11 +334,8 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); if (!is_null) { - Name label_name; - label = palloc(sizeof(*label)); - label_name = DatumGetName(datum); - label->name = pstrdup(NameStr(*label_name)); + namestrcpy(&label->name, NameStr(*DatumGetName(datum))); datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, &is_null); @@ -611,7 +593,7 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) TupleDesc tupdesc; label_entry = lfirst(lc); - vertex_label_name = label_entry->name; + vertex_label_name = NameStr(label_entry->name); vertex_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_vertex_label = table_open(vertex_label_table_oid, AccessShareLock); @@ -718,7 +700,7 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx) TupleDesc tupdesc; label_entry = lfirst(lc); - edge_label_name = label_entry->name; + edge_label_name = NameStr(label_entry->name); edge_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ graph_edge_label = table_open(edge_label_table_oid, AccessShareLock); @@ -1016,7 +998,7 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( } /* otherwise, we need to create one and possibly attach it */ - new_ggctx = palloc0(sizeof(GRAPH_global_context)); + new_ggctx = palloc(sizeof(GRAPH_global_context)); if (global_graph_contexts_container.contexts != NULL) { @@ -1038,9 +1020,13 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( new_ggctx->graph_version = get_graph_version(graph_oid); /* set snapshot fields for SNAPSHOT fallback mode */ - new_ggctx->xmin = GetActiveSnapshot()->xmin; - new_ggctx->xmax = GetActiveSnapshot()->xmax; - new_ggctx->curcid = GetActiveSnapshot()->curcid; + { + Snapshot snap = GetActiveSnapshot(); + + new_ggctx->xmin = snap->xmin; + new_ggctx->xmax = snap->xmax; + new_ggctx->curcid = snap->curcid; + } /* initialize our vertices list */ new_ggctx->vertices = NULL; @@ -1137,6 +1123,8 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && graph_name_len < NAMEDATALEN && @@ -1147,14 +1135,28 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, return cached_graph_oid; } - graph_name_cstr = pnstrdup(graph_name, graph_name_len); + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { namestrcpy(&cached_graph_name, graph_name_cstr); - cached_generation = get_graph_cache_generation(); + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); } - pfree(graph_name_cstr); return cached_graph_oid; } diff --git a/src/backend/utils/adt/age_graphid_ds.c b/src/backend/utils/adt/age_graphid_ds.c index cee53bd83..2e6d4612b 100644 --- a/src/backend/utils/adt/age_graphid_ds.c +++ b/src/backend/utils/adt/age_graphid_ds.c @@ -100,7 +100,7 @@ ListGraphId *append_graphid(ListGraphId *container, graphid id) GraphIdNode *new_node = NULL; /* create the new link */ - new_node = palloc0(sizeof(GraphIdNode)); + new_node = palloc(sizeof(GraphIdNode)); new_node->id = id; new_node->next = NULL; @@ -110,7 +110,7 @@ ListGraphId *append_graphid(ListGraphId *container, graphid id) */ if (container == NULL) { - container = palloc0(sizeof(ListGraphId)); + container = palloc(sizeof(ListGraphId)); container->head = new_node; container->tail = new_node; container->size = 1; @@ -160,7 +160,7 @@ ListGraphId *new_graphid_stack(void) ListGraphId *stack = NULL; /* allocate the container for the stack */ - stack = palloc0(sizeof(ListGraphId)); + stack = palloc(sizeof(ListGraphId)); /* set it to its initial values */ stack->head = NULL; @@ -214,7 +214,7 @@ void push_graphid_stack(ListGraphId *stack, graphid id) } /* create the new element */ - new_node = palloc0(sizeof(GraphIdNode)); + new_node = palloc(sizeof(GraphIdNode)); new_node->id = id; /* insert (push) the new element on the top */ diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 34a778165..5eb06425b 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -81,7 +81,8 @@ #define EDGE_STATE_HTAB_NAME "Edge state " #define EDGE_STATE_HTAB_MIN_SIZE 16 #define EXISTS_HTAB_NAME "known edges" -#define EXISTS_HTAB_NAME_INITIAL_SIZE 1000 +#define EXISTS_HTAB_NAME_MIN_SIZE 16 +#define EDGE_UNIQUENESS_FAST_ARGS 16 #define MAXIMUM_NUMBER_OF_CACHED_LOCAL_CONTEXTS 5 /* edge state entry for the edge_state_hashtable */ @@ -153,6 +154,12 @@ typedef struct VLE_path_container graphid graphid_array_data; } VLE_path_container; +typedef struct edge_uniqueness_argtype_cache +{ + int nargs; + Oid types[FLEXIBLE_ARRAY_MEMBER]; +} edge_uniqueness_argtype_cache; + /* declarations */ /* global variable to hold the per process global cached VLE_local contexts */ @@ -178,6 +185,12 @@ static edge_state_entry *get_edge_state(VLE_local_context *vlelctx, static void load_initial_dfs_stacks(VLE_local_context *vlelctx); static bool dfs_find_a_path_between(VLE_local_context *vlelctx); static bool dfs_find_a_path_from(VLE_local_context *vlelctx); +static int get_edge_uniqueness_args_fast(FunctionCallInfo fcinfo, + Datum **args, Oid **types, + bool **nulls, Datum *fast_args, + Oid *fast_types, bool *fast_nulls); +static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, + int argno, int nargs); static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx); static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id); @@ -350,20 +363,10 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) char *graph_name = NULL; char *eshn = NULL; long initial_size; - int glen; - int elen; - /* get the graph name and length */ + /* get the graph name */ graph_name = vlelctx->graph_name; - glen = strlen(graph_name); - /* get the edge state htab name length */ - elen = strlen(EDGE_STATE_HTAB_NAME); - /* allocate the space and build the name */ - eshn = palloc0(elen + glen + 1); - /* copy in the name */ - strcpy(eshn, EDGE_STATE_HTAB_NAME); - /* add in the graph name */ - eshn = strncat(eshn, graph_name, glen); + eshn = psprintf("%s%s", EDGE_STATE_HTAB_NAME, graph_name); /* initialize the edge state hashtable */ MemSet(&edge_state_ctl, 0, sizeof(edge_state_ctl)); @@ -760,7 +763,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, graph_oid); /* allocate and initialize local VLE context */ - vlelctx = palloc0(sizeof(VLE_local_context)); + vlelctx = palloc(sizeof(VLE_local_context)); /* store the cache usage */ vlelctx->use_cache = use_cache; @@ -940,6 +943,8 @@ static Oid get_cached_vle_graph_oid(const char *graph_name, static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && graph_name_len < NAMEDATALEN && @@ -950,14 +955,28 @@ static Oid get_cached_vle_graph_oid(const char *graph_name, return cached_graph_oid; } - graph_name_cstr = pnstrdup(graph_name, graph_name_len); + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { namestrcpy(&cached_graph_name, graph_name_cstr); - cached_generation = get_graph_cache_generation(); + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); } - pfree(graph_name_cstr); return cached_graph_oid; } @@ -1511,7 +1530,7 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) container_size_bytes = sizeof(graphid) * (path_size + 4); /* allocate the container */ - vpc = palloc0(container_size_bytes); + vpc = palloc(container_size_bytes); /* initialize the PG headers */ SET_VARSIZE(vpc, container_size_bytes); @@ -1521,7 +1540,6 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) vpc->graphid_array_size = path_size; vpc->container_size_bytes = container_size_bytes; - /* the graphid array is already zeroed out */ /* all of the other fields are set by the caller */ return vpc; @@ -2664,6 +2682,76 @@ Datum _ag_enforce_edge_uniqueness4(PG_FUNCTION_ARGS) PG_RETURN_BOOL(true); } +static int get_edge_uniqueness_args_fast(FunctionCallInfo fcinfo, + Datum **args, Oid **types, + bool **nulls, Datum *fast_args, + Oid *fast_types, bool *fast_nulls) +{ + int nargs; + int i; + + if (get_fn_expr_variadic(fcinfo->flinfo)) + { + return extract_variadic_args(fcinfo, 0, true, args, types, nulls); + } + + nargs = PG_NARGS(); + if (nargs > EDGE_UNIQUENESS_FAST_ARGS) + { + return extract_variadic_args(fcinfo, 0, true, args, types, nulls); + } + + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_types[i] = get_cached_edge_uniqueness_argtype(fcinfo, i, nargs); + + if (fast_types[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + fast_types[i] = TEXTOID; + fast_args[i] = fast_nulls[i] ? (Datum)0 : + CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + else + { + fast_args[i] = PG_GETARG_DATUM(i); + } + } + + *args = fast_args; + *types = fast_types; + *nulls = fast_nulls; + + return nargs; +} + +static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, + int argno, int nargs) +{ + edge_uniqueness_argtype_cache *cache; + int i; + + cache = (edge_uniqueness_argtype_cache *)fcinfo->flinfo->fn_extra; + if (cache == NULL || cache->nargs != nargs) + { + cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(edge_uniqueness_argtype_cache, + types) + + sizeof(Oid) * nargs); + cache->nargs = nargs; + + for (i = 0; i < nargs; i++) + { + cache->types[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + } + + fcinfo->flinfo->fn_extra = cache; + } + + return cache->types[argno]; +} + /* * This function checks the edges in a MATCH clause to see if they are unique or * not. Filters out all the paths where the edge uniques rules are not met. @@ -2675,14 +2763,19 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) { HTAB *exists_hash = NULL; HASHCTL exists_ctl; + Datum fast_args[EDGE_UNIQUENESS_FAST_ARGS]; + Oid fast_types[EDGE_UNIQUENESS_FAST_ARGS]; + bool fast_nulls[EDGE_UNIQUENESS_FAST_ARGS]; Datum *args = NULL; bool *nulls = NULL; Oid *types = NULL; int nargs = 0; + int64 estimated_edges = 0; int i = 0; /* extract our arguments */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_edge_uniqueness_args_fast(fcinfo, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* verify the arguments */ for (i = 0; i < nargs; i++) @@ -2703,6 +2796,32 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) errmsg("_ag_enforce_edge_uniqueness argument %d must be AGTYPE, INT8, or GRAPHIDOID", i))); } + + if (types[i] == INT8OID || types[i] == GRAPHIDOID) + { + estimated_edges++; + } + else if (types[i] == AGTYPEOID) + { + agtype *agt_i = DATUM_GET_AGTYPE_P(args[i]); + + if (AGT_ROOT_IS_BINARY(agt_i) && + AGT_ROOT_BINARY_FLAGS(agt_i) == AGT_FBINARY_TYPE_VLE_PATH) + { + VLE_path_container *vpc = (VLE_path_container *)agt_i; + + estimated_edges += vpc->graphid_array_size / 2; + } + else + { + estimated_edges++; + } + } + } + + if (estimated_edges < 2) + { + PG_RETURN_BOOL(true); } /* configure the hash table */ @@ -2712,7 +2831,8 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) exists_ctl.hash = tag_hash; /* create exists_hash table */ - exists_hash = hash_create(EXISTS_HTAB_NAME, EXISTS_HTAB_NAME_INITIAL_SIZE, + exists_hash = hash_create(EXISTS_HTAB_NAME, + Max(estimated_edges, EXISTS_HTAB_NAME_MIN_SIZE), &exists_ctl, HASH_ELEM | HASH_FUNCTION); /* insert arguments into hash table */ diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index be82423b8..50e094101 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -112,9 +112,27 @@ typedef enum /* type categories for datum_to_agtype */ AGT_TYPE_OTHER /* all else */ } agt_type_category; +typedef struct agtype_variadic_argtype_cache +{ + int nargs; + Oid types[FLEXIBLE_ARRAY_MEMBER]; +} agtype_variadic_argtype_cache; + static inline Datum agtype_from_cstring(char *str, int len); size_t check_string_length(size_t len); static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno); +static bool get_single_variadic_arg(FunctionCallInfo fcinfo, + const char *funcname, + bool convert_unknown, + Datum *arg, Oid *type); +static int get_variadic_args_fast(FunctionCallInfo fcinfo, + bool convert_unknown, + int max_args, + Datum **args, Oid **types, bool **nulls, + Datum *fast_args, Oid *fast_types, + bool *fast_nulls); +static Oid get_cached_agtype_variadic_argtype(FunctionCallInfo fcinfo, + int argno, int nargs); static void agtype_in_agtype_annotation(void *pstate, char *annotation); static void agtype_in_object_start(void *pstate); static void agtype_in_object_end(void *pstate); @@ -211,6 +229,8 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo); agtype_value *agtype_composite_to_agtype_value_binary(agtype *a); static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr); +static bool agtype_string_contains(char *haystack, int haystack_len, + char *needle, int needle_len); void *repalloc_check(void *ptr, size_t len) @@ -236,6 +256,40 @@ void pfree_if_not_null(void *ptr) } } +static bool agtype_string_contains(char *haystack, int haystack_len, + char *needle, int needle_len) +{ + char *match; + char *end; + + if (needle_len == 0) + { + return true; + } + + if (haystack_len < needle_len) + { + return false; + } + + end = haystack + haystack_len - needle_len + 1; + for (match = haystack; match < end; match++) + { + match = memchr(match, needle[0], end - match); + if (match == NULL) + { + return false; + } + + if (memcmp(match, needle, needle_len) == 0) + { + return true; + } + } + + return false; +} + /* global storage of OID for agtype and _agtype */ static Oid g_AGTYPEOID = InvalidOid; static Oid g_AGTYPEARRAYOID = InvalidOid; @@ -515,6 +569,139 @@ static Oid get_cached_fn_expr_argtype(FunctionCallInfo fcinfo, int argno) return *(Oid *)fcinfo->flinfo->fn_extra; } +static bool get_single_variadic_arg(FunctionCallInfo fcinfo, + const char *funcname, + bool convert_unknown, + Datum *arg, Oid *type) +{ + int nargs; + Datum *args = NULL; + bool *nulls = NULL; + Oid *types = NULL; + + if (PG_NARGS() == 1 && !get_fn_expr_variadic(fcinfo->flinfo)) + { + if (PG_ARGISNULL(0)) + { + return false; + } + + *arg = PG_GETARG_DATUM(0); + *type = get_cached_fn_expr_argtype(fcinfo, 0); + if (convert_unknown && + *type == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, 0)) + { + *type = TEXTOID; + *arg = CStringGetTextDatum(PG_GETARG_POINTER(0)); + } + return true; + } + + nargs = extract_variadic_args(fcinfo, 0, convert_unknown, + &args, &types, &nulls); + + if (nargs > 1) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports one argument", funcname))); + } + + if (nargs < 1 || nulls[0]) + { + return false; + } + + *arg = args[0]; + *type = types[0]; + + return true; +} + +static int get_variadic_args_fast(FunctionCallInfo fcinfo, + bool convert_unknown, + int max_args, + Datum **args, Oid **types, bool **nulls, + Datum *fast_args, Oid *fast_types, + bool *fast_nulls) +{ + int nargs; + int i; + + if (!get_fn_expr_variadic(fcinfo->flinfo)) + { + nargs = PG_NARGS(); + + if (nargs > max_args) + { + return extract_variadic_args(fcinfo, 0, convert_unknown, + args, types, nulls); + } + + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_types[i] = get_cached_agtype_variadic_argtype(fcinfo, i, + nargs); + + if (convert_unknown && + fast_types[i] == UNKNOWNOID && + get_fn_expr_arg_stable(fcinfo->flinfo, i)) + { + fast_types[i] = TEXTOID; + + if (fast_nulls[i]) + { + fast_args[i] = (Datum)0; + } + else + { + fast_args[i] = CStringGetTextDatum(PG_GETARG_POINTER(i)); + } + } + else + { + fast_args[i] = PG_GETARG_DATUM(i); + } + } + + *args = fast_args; + *types = fast_types; + *nulls = fast_nulls; + + return nargs; + } + + return extract_variadic_args(fcinfo, 0, convert_unknown, + args, types, nulls); +} + +static Oid get_cached_agtype_variadic_argtype(FunctionCallInfo fcinfo, + int argno, int nargs) +{ + agtype_variadic_argtype_cache *cache; + int i; + + cache = (agtype_variadic_argtype_cache *)fcinfo->flinfo->fn_extra; + if (cache == NULL || cache->nargs != nargs) + { + cache = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, + offsetof(agtype_variadic_argtype_cache, + types) + + sizeof(Oid) * nargs); + cache->nargs = nargs; + + for (i = 0; i < nargs; i++) + { + cache->types[i] = get_fn_expr_argtype(fcinfo->flinfo, i); + } + + fcinfo->flinfo->fn_extra = cache; + } + + return cache->types[argno]; +} + size_t check_string_length(size_t len) { if (len > AGTENTRY_OFFLENMASK) @@ -2080,7 +2267,7 @@ void add_agtype(Datum val, bool is_null, agtype_in_state *result, agtype_value *string_to_agtype_value(char *s) { - agtype_value *agtv = palloc0(sizeof(agtype_value)); + agtype_value *agtv = palloc(sizeof(agtype_value)); agtv->type = AGTV_STRING; agtv->val.string.len = check_string_length(strlen(s)); @@ -2092,7 +2279,7 @@ agtype_value *string_to_agtype_value(char *s) /* helper function to create an agtype_value integer from an integer */ agtype_value *integer_to_agtype_value(int64 int_value) { - agtype_value *agtv = palloc0(sizeof(agtype_value)); + agtype_value *agtv = palloc(sizeof(agtype_value)); agtv->type = AGTV_INTEGER; agtv->val.int_value = int_value; @@ -2110,14 +2297,18 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) agtype_in_state result; agtype *agt_result; Datum *args = NULL; + Datum fast_args[16]; bool *nulls = NULL; + bool fast_nulls[16]; Oid *types = NULL; + Oid fast_types[16]; int nargs = 0; int i = 0; bool is_zero_boundary_case = false; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 1) { @@ -2540,11 +2731,15 @@ static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) int i; agtype_in_state result; Datum *args; + Datum fast_args[16]; bool *nulls; + bool fast_nulls[16]; Oid *types; + Oid fast_types[16]; /* build argument values to build the object */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 0) { @@ -2691,12 +2886,16 @@ Datum agtype_build_list(PG_FUNCTION_ARGS) int i; agtype_in_state result; Datum *args; + Datum fast_args[16]; bool *nulls; + bool fast_nulls[16]; Oid *types; + Oid fast_types[16]; agtype *agt_result; /*build argument values to build the array */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 16, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); if (nargs < 0) { @@ -3752,8 +3951,8 @@ agtype_value *get_agtype_value_object_value(const agtype_value *agtv_object, } /* they are the same length so compare the keys */ - result = strncmp(search_key, agtv_key->val.string.val, - search_key_length); + result = memcmp(search_key, agtv_key->val.string.val, + search_key_length); /* if they don't match */ if (result != 0) @@ -3840,7 +4039,7 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, &args_res, &nulls_res, &nargs); /* All the elements of the array have the same type */ - types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + types_res = (Oid *) palloc(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) { types_res[i] = element_type; @@ -3859,14 +4058,15 @@ static int extract_variadic_args_min(FunctionCallInfo fcinfo, } /* allocate result memory */ - nulls_res = (bool *) palloc0(nargs * sizeof(bool)); - args_res = (Datum *) palloc0(nargs * sizeof(Datum)); - types_res = (Oid *) palloc0(nargs * sizeof(Oid)); + nulls_res = (bool *) palloc(nargs * sizeof(bool)); + args_res = (Datum *) palloc(nargs * sizeof(Datum)); + types_res = (Oid *) palloc(nargs * sizeof(Oid)); for (i = 0; i < nargs; i++) { nulls_res[i] = PG_ARGISNULL(i + variadic_start); - types_res[i] = get_fn_expr_argtype(fcinfo->flinfo, i + variadic_start); + types_res[i] = get_cached_agtype_variadic_argtype( + fcinfo, i + variadic_start, PG_NARGS()); /* * Turn a constant (more or less literal) value that's of unknown @@ -4164,6 +4364,8 @@ PG_FUNCTION_INFO_V1(agtype_access_operator); */ Datum agtype_access_operator(PG_FUNCTION_ARGS) { + Datum fast_args[16]; + bool fast_nulls[16]; Datum *args = NULL; bool *nulls = NULL; Oid *types = NULL; @@ -4172,6 +4374,7 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) agtype_value *container_value = NULL; agtype *result = NULL; int i = 0; + bool using_fast_args = false; /* * Fast path for the common 2-argument case (object.property or @@ -4283,8 +4486,29 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) */ /* extract our args, we need at least 2 */ - nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, &nulls, - 2); + if (!get_fn_expr_variadic(fcinfo->flinfo) && + PG_NARGS() <= lengthof(fast_args)) + { + nargs = PG_NARGS(); + if (nargs >= 2) + { + for (i = 0; i < nargs; i++) + { + fast_nulls[i] = PG_ARGISNULL(i); + fast_args[i] = PG_GETARG_DATUM(i); + } + + args = fast_args; + nulls = fast_nulls; + using_fast_args = true; + } + } + else + { + nargs = extract_variadic_args_min(fcinfo, 0, true, &args, &types, + &nulls, 2); + } + /* * Return NULL if - * @@ -4297,9 +4521,12 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) */ if (args == NULL || nargs == 0 || nulls[0] == true) { - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } @@ -4310,9 +4537,12 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* if we have a NULL, return NULL */ if (nulls[i] == true) { - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } } @@ -4431,9 +4661,12 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) container = NULL; } - pfree_if_not_null(args); - pfree_if_not_null(types); - pfree_if_not_null(nulls); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } /* serialize and return the result */ result = agtype_value_to_agtype(container_value); @@ -4793,9 +5026,9 @@ Datum agtype_string_match_starts_with(PG_FUNCTION_ARGS) { result = false; } - else if (strncmp(lhs_value->val.string.val, - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp(lhs_value->val.string.val, + rhs_value->val.string.val, + rhs_value->val.string.len) == 0) { result = true; } @@ -4843,11 +5076,11 @@ Datum agtype_string_match_ends_with(PG_FUNCTION_ARGS) { result = false; } - else if (strncmp((lhs_value->val.string.val + - lhs_value->val.string.len - - rhs_value->val.string.len), - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp((lhs_value->val.string.val + + lhs_value->val.string.len - + rhs_value->val.string.len), + rhs_value->val.string.val, + rhs_value->val.string.len) == 0) { result = true; } @@ -4891,27 +5124,10 @@ Datum agtype_string_match_contains(PG_FUNCTION_ARGS) if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) { - char *l; - char *r; - - if (lhs_value->val.string.len < rhs_value->val.string.len) - { - result = false; - } - - l = pnstrdup(lhs_value->val.string.val, lhs_value->val.string.len); - r = pnstrdup(rhs_value->val.string.val, rhs_value->val.string.len); - - if (strstr(l, r) == NULL) - { - result = false; - } - else - { - result = true; - } - pfree_if_not_null(l); - pfree_if_not_null(r); + result = agtype_string_contains(lhs_value->val.string.val, + lhs_value->val.string.len, + rhs_value->val.string.val, + rhs_value->val.string.len); } pfree_agtype_value(lhs_value); pfree_agtype_value(rhs_value); @@ -4940,7 +5156,7 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) agtype *agt; agtype_iterator *it; agtype_iterator_token tok; - agtype_value *r; + agtype_value r; uint64 seed = 0xF0F0F0F0; /* this function returns INTEGER which is 32 bits */ @@ -4951,18 +5167,16 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) agt = AG_GET_ARG_AGTYPE_P(0); - r = palloc0(sizeof(agtype_value)); - it = agtype_iterator_init(&agt->root); - while ((tok = agtype_iterator_next(&it, r, false)) != WAGT_DONE) + while ((tok = agtype_iterator_next(&it, &r, false)) != WAGT_DONE) { - if (IS_A_AGTYPE_SCALAR(r) && AGTYPE_ITERATOR_TOKEN_IS_HASHABLE(tok)) - agtype_hash_scalar_value_extended(r, &hash, seed); - else if (tok == WAGT_BEGIN_ARRAY && !r->val.array.raw_scalar) + if (IS_A_AGTYPE_SCALAR(&r) && AGTYPE_ITERATOR_TOKEN_IS_HASHABLE(tok)) + agtype_hash_scalar_value_extended(&r, &hash, seed); + else if (tok == WAGT_BEGIN_ARRAY && !r.val.array.raw_scalar) seed = LEFT_ROTATE(seed, 4); else if (tok == WAGT_BEGIN_OBJECT) seed = LEFT_ROTATE(seed, 6); - else if (tok == WAGT_END_ARRAY && !r->val.array.raw_scalar) + else if (tok == WAGT_END_ARRAY && !r.val.array.raw_scalar) seed = RIGHT_ROTATE(seed, 4); else if (tok == WAGT_END_OBJECT) seed = RIGHT_ROTATE(seed, 4); @@ -4970,7 +5184,6 @@ Datum agtype_hash_cmp(PG_FUNCTION_ARGS) seed = LEFT_ROTATE(seed, 1); } - pfree_if_not_null(r); PG_FREE_IF_COPY(agt, 0); PG_RETURN_INT32(hash); @@ -5067,10 +5280,8 @@ Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) /* this allows string numbers and NaN */ case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value->val.string.val, + arg_value->val.string.len); /* pass the string to the numeric in function for conversion */ numd = DirectFunctionCall3(numeric_in, CStringGetDatum(string), @@ -5158,10 +5369,8 @@ Datum agtype_typecast_int(PG_FUNCTION_ARGS) break; case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value->val.string.val, + arg_value->val.string.len); d = DirectFunctionCall1(int8in, CStringGetDatum(string)); /* free the string */ @@ -5295,10 +5504,8 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) /* this allows string numbers, NaN, Infinity, and -Infinity */ case AGTV_STRING: /* we need a null terminated string */ - string = (char *) palloc0(sizeof(char)*arg_value->val.string.len + 1); - string = strncpy(string, arg_value->val.string.val, - arg_value->val.string.len); - string[arg_value->val.string.len] = '\0'; + string = pnstrdup(arg_value->val.string.val, + arg_value->val.string.len); d = DirectFunctionCall1(float8in, CStringGetDatum(string)); /* free the string */ @@ -5729,6 +5936,8 @@ static Oid get_cached_graph_oid_for_name(const char *graph_name, static uint64 cached_generation = 0; uint64 current_generation = get_graph_cache_generation(); char *graph_name_cstr; + NameData graph_name_buf; + bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && graph_name_len < NAMEDATALEN && @@ -5739,14 +5948,28 @@ static Oid get_cached_graph_oid_for_name(const char *graph_name, return cached_graph_oid; } - graph_name_cstr = pnstrdup(graph_name, graph_name_len); + if (graph_name_len < NAMEDATALEN) + { + memcpy(NameStr(graph_name_buf), graph_name, graph_name_len); + NameStr(graph_name_buf)[graph_name_len] = '\0'; + graph_name_cstr = NameStr(graph_name_buf); + } + else + { + graph_name_cstr = pnstrdup(graph_name, graph_name_len); + free_graph_name = true; + } + cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { namestrcpy(&cached_graph_name, graph_name_cstr); - cached_generation = get_graph_cache_generation(); + cached_generation = current_generation; + } + if (free_graph_name) + { + pfree(graph_name_cstr); } - pfree(graph_name_cstr); return cached_graph_oid; } @@ -6361,35 +6584,21 @@ PG_FUNCTION_INFO_V1(age_toboolean); Datum age_toboolean(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; Oid type; agtype_value agtv_result; char *string = NULL; bool result = false; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toBoolean() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toBoolean()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* * toBoolean() supports bool, text, cstring, integer or the agtype bool, * string and integer input. */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == BOOLOID) @@ -6574,36 +6783,22 @@ PG_FUNCTION_INFO_V1(age_tofloat); Datum age_tofloat(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; bool is_valid = false; Oid type; float8 result; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toFloat() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toFloat()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* * toFloat() supports integer, float, numeric, text, cstring, or the * agtype integer, float, numeric, and string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == INT2OID) @@ -6805,62 +7000,16 @@ PG_FUNCTION_INFO_V1(age_tointeger); Datum age_tointeger(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; bool is_valid = false; Oid type; int64 result; - /* - * Fast path: toInteger() always takes exactly 1 argument. - * Avoid extract_variadic_args overhead by accessing the arg directly - * and caching the type via fn_extra. - */ - if (PG_NARGS() == 1) - { - if (PG_ARGISNULL(0)) - { - PG_RETURN_NULL(); - } - - arg = PG_GETARG_DATUM(0); - - /* cache the arg type on first call */ - if (fcinfo->flinfo->fn_extra == NULL) - { - Oid *cached = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, - sizeof(Oid)); - *cached = get_fn_expr_argtype(fcinfo->flinfo, 0); - fcinfo->flinfo->fn_extra = cached; - } - type = *(Oid *)fcinfo->flinfo->fn_extra; - nargs = 1; - } - else + if (!get_single_variadic_arg(fcinfo, "toInteger()", true, &arg, &type)) { - /* fallback variadic path */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toInteger() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) - { - PG_RETURN_NULL(); - } - - arg = args[0]; - type = types[0]; + PG_RETURN_NULL(); } if (type != AGTYPEOID) @@ -7180,28 +7329,13 @@ PG_FUNCTION_INFO_V1(age_size); Datum age_size(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; Oid type; int64 result; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("size() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "size()", true, &arg, &type)) { PG_RETURN_NULL(); } @@ -7209,9 +7343,6 @@ Datum age_size(PG_FUNCTION_ARGS) /* * size() supports cstring, text, or the agtype string or list input */ - arg = args[0]; - type = types[0]; - if (type == CSTRINGOID) { string = DatumGetCString(arg); @@ -7357,23 +7488,19 @@ PG_FUNCTION_INFO_V1(age_isempty); Datum age_isempty(PG_FUNCTION_ARGS) { - Datum *args; Datum arg; - bool *nulls; - Oid *types; char *string = NULL; Oid type; int64 result; - /* extract argument values */ - extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + if (!get_single_variadic_arg(fcinfo, "isEmpty()", true, &arg, &type)) + { + PG_RETURN_NULL(); + } /* * isEmpty() supports cstring, text, or the agtype string or list input */ - arg = args[0]; - type = types[0]; - if (type == CSTRINGOID) { string = DatumGetCString(arg); @@ -7559,8 +7686,6 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) agtype_value *agtv_result = NULL; char *string = NULL; - agtv_result = palloc0(sizeof(agtype_value)); - /* * toString() supports: unknown, integer, float, numeric, text, cstring, * boolean, regtype or the agtypes: integer, float, numeric, string, and @@ -7691,6 +7816,7 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } /* build the result */ + agtv_result = palloc(sizeof(agtype_value)); agtv_result->type = AGTV_STRING; agtv_result->val.string.val = string; agtv_result->val.string.len = strlen(string); @@ -7851,37 +7977,19 @@ PG_FUNCTION_INFO_V1(age_reverse); Datum age_reverse(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("reverse() only supports one argument"))); - } - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "reverse()", true, &arg, &type)) { PG_RETURN_NULL(); } /* reverse() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8018,11 +8126,7 @@ PG_FUNCTION_INFO_V1(age_toupper); Datum age_toupper(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; char *result = NULL; @@ -8030,21 +8134,12 @@ Datum age_toupper(PG_FUNCTION_ARGS) Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toUpper() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toUpper()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* toUpper() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8086,7 +8181,7 @@ Datum age_toupper(PG_FUNCTION_ARGS) } /* allocate the new string */ - result = palloc0(string_len); + result = palloc(string_len); /* upcase the string */ for (i = 0; i < string_len; i++) @@ -8104,11 +8199,7 @@ PG_FUNCTION_INFO_V1(age_tolower); Datum age_tolower(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; char *string = NULL; char *result = NULL; @@ -8116,21 +8207,12 @@ Datum age_tolower(PG_FUNCTION_ARGS) Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("toLower() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "toLower()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* toLower() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8172,7 +8254,7 @@ Datum age_tolower(PG_FUNCTION_ARGS) } /* allocate the new string */ - result = palloc0(string_len); + result = palloc(string_len); /* downcase the string */ for (i = 0; i < string_len; i++) @@ -8190,33 +8272,19 @@ PG_FUNCTION_INFO_V1(age_rtrim); Datum age_rtrim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "rTrim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* rTrim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8277,33 +8345,19 @@ PG_FUNCTION_INFO_V1(age_ltrim); Datum age_ltrim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "lTrim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* rTrim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8364,33 +8418,19 @@ PG_FUNCTION_INFO_V1(age_trim); Datum age_trim(PG_FUNCTION_ARGS) { - int nargs; - Datum *args; Datum arg; - bool *nulls; - Oid *types; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); - - /* check number of args */ - if (nargs > 1) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() only supports one argument"))); - - /* check for null */ - if (nargs < 0 || nulls[0]) + if (!get_single_variadic_arg(fcinfo, "trim()", true, &arg, &type)) + { PG_RETURN_NULL(); + } /* trim() supports text, cstring, or the agtype string input */ - arg = args[0]; - type = types[0]; - if (type != AGTYPEOID) { if (type == CSTRINGOID) @@ -8453,17 +8493,20 @@ Datum age_right(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int64 string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -8624,17 +8667,20 @@ Datum age_left(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; int64 string_len; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -8802,9 +8848,12 @@ Datum age_substring(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[3]; Datum arg; bool *nulls; + bool fast_nulls[3]; Oid *types; + Oid fast_types[3]; agtype_value agtv_result; text *text_string = NULL; char *string = NULL; @@ -8814,8 +8863,8 @@ Datum age_substring(PG_FUNCTION_ARGS) int i; Oid type; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs < 2 || nargs > 3) @@ -9020,9 +9069,12 @@ Datum age_split(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; Datum arg; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value *agtv_result; text *param = NULL; text *text_string = NULL; @@ -9031,8 +9083,8 @@ Datum age_split(PG_FUNCTION_ARGS) Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -9134,7 +9186,7 @@ Datum age_split(PG_FUNCTION_ARGS) string_len = VARSIZE(elements[i]) - VARHDRSZ; /* make a copy */ - string_copy = palloc0(string_len); + string_copy = palloc(string_len); memcpy(string_copy, string, string_len); /* build the agtype string */ @@ -9168,9 +9220,12 @@ Datum age_replace(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[3]; Datum arg; bool *nulls; + bool fast_nulls[3]; Oid *types; + Oid fast_types[3]; agtype_value agtv_result; text *param = NULL; text *text_string = NULL; @@ -9182,8 +9237,8 @@ Datum age_replace(PG_FUNCTION_ARGS) Oid type; int i; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 3) @@ -9470,15 +9525,18 @@ Datum age_sin(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9517,15 +9575,18 @@ Datum age_cos(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9564,15 +9625,18 @@ Datum age_tan(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9611,15 +9675,18 @@ Datum age_cot(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle; float8 result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9658,15 +9725,18 @@ Datum age_asin(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9709,15 +9779,18 @@ Datum age_acos(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9760,15 +9833,18 @@ Datum age_atan(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 x; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9807,15 +9883,18 @@ Datum age_atan2(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[2]; bool *nulls; + bool fast_nulls[2]; Oid *types; + Oid fast_types[2]; agtype_value agtv_result; float8 x, y; float8 angle; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 2) @@ -9861,15 +9940,18 @@ Datum age_degrees(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle_degrees; float8 angle_radians; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9908,15 +9990,18 @@ Datum age_radians(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; float8 angle_degrees; float8 angle_radians; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -9955,8 +10040,11 @@ PG_FUNCTION_INFO_V1(age_round); Datum age_round(PG_FUNCTION_ARGS) { Datum *args = NULL; + Datum fast_args[2]; bool *nulls = NULL; + bool fast_nulls[2]; Oid *types = NULL; + Oid fast_types[2]; int nargs = 0; agtype_value agtv_result; Numeric arg, arg_precision; @@ -9965,8 +10053,8 @@ Datum age_round(PG_FUNCTION_ARGS) bool is_null = true; int precision = 0; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 2, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1 && nargs != 2) @@ -10037,16 +10125,19 @@ Datum age_ceil(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10086,16 +10177,19 @@ Datum age_floor(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10136,16 +10230,19 @@ Datum age_abs(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; bool is_null = true; enum agtype_value_type type = AGTV_NULL; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10209,16 +10306,19 @@ Datum age_sign(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; int int_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10259,8 +10359,11 @@ Datum age_log(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10269,8 +10372,8 @@ Datum age_log(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10320,8 +10423,11 @@ Datum age_log10(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10331,8 +10437,8 @@ Datum age_log10(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10435,16 +10541,19 @@ Datum age_exp(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric numeric_result; float8 float_result; bool is_null = true; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10484,8 +10593,11 @@ Datum age_sqrt(PG_FUNCTION_ARGS) { int nargs; Datum *args; + Datum fast_args[1]; bool *nulls; + bool fast_nulls[1]; Oid *types; + Oid fast_types[1]; agtype_value agtv_result; Numeric arg; Numeric zero; @@ -10494,8 +10606,8 @@ Datum age_sqrt(PG_FUNCTION_ARGS) bool is_null = true; int test; - /* extract argument values */ - nargs = extract_variadic_args(fcinfo, 0, true, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, true, 1, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* check number of args */ if (nargs != 1) @@ -10593,10 +10705,11 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, agtype_iterator *it; agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *r; + agtype_value r; agtype *prop_agtype; agtype_value *parsed_agtype_value = NULL; bool found; + int var_name_len; /* if no properties, return NULL */ if (properties == NULL) @@ -10611,13 +10724,14 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, errmsg("can only update objects"))); } - r = palloc0(sizeof(agtype_value)); + var_name_len = strlen(var_name); prop_agtype = agtype_value_to_agtype(properties); it = agtype_iterator_init(&prop_agtype->root); - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); - parsed_agtype_value = push_agtype_value(&parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + parsed_agtype_value = push_agtype_value(&parse_state, tok, + tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* * If the new value is NULL, this is equivalent to the remove_property @@ -10631,16 +10745,17 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, found = false; while (true) { - char *str; + bool key_matches; - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); if (tok == WAGT_DONE || tok == WAGT_END_OBJECT) { break; } - str = pnstrdup(r->val.string.val, r->val.string.len); + key_matches = (r.val.string.len == var_name_len && + memcmp(r.val.string.val, var_name, var_name_len) == 0); /* * Check the key value, if it is equal to the passed in @@ -10648,15 +10763,15 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, * in agtype. Otherwise pass the existing value to the * new properties agtype_value. */ - if (strcmp(str, var_name)) + if (!key_matches) { /* push the key */ parsed_agtype_value = push_agtype_value( - &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* get the value and push the value */ - tok = agtype_iterator_next(&it, r, true); - parsed_agtype_value = push_agtype_value(&parse_state, tok, r); + tok = agtype_iterator_next(&it, &r, true); + parsed_agtype_value = push_agtype_value(&parse_state, tok, &r); } else { @@ -10666,16 +10781,16 @@ agtype_value *alter_property_value(agtype_value *properties, char *var_name, if(remove_property) { /* skip the value */ - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); continue; } /* push the key */ parsed_agtype_value = push_agtype_value( - &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? r : NULL); + &parse_state, tok, tok < WAGT_BEGIN_ARRAY ? &r : NULL); /* skip the existing value for the key */ - tok = agtype_iterator_next(&it, r, true); + tok = agtype_iterator_next(&it, &r, true); /* * If the new agtype is scalar, push the agtype_value to the @@ -10756,8 +10871,8 @@ agtype_value *alter_properties(agtype_value *original_properties, agtype_iterator *it; agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *key; - agtype_value *value; + agtype_value key; + agtype_value value; agtype_value *parsed_agtype_value = NULL; parsed_agtype_value = push_agtype_value(&parse_state, WAGT_BEGIN_OBJECT, @@ -10777,10 +10892,8 @@ agtype_value *alter_properties(agtype_value *original_properties, } /* Append new properties. */ - key = palloc0(sizeof(agtype_value)); - value = palloc0(sizeof(agtype_value)); it = agtype_iterator_init(&new_properties->root); - tok = agtype_iterator_next(&it, key, true); + tok = agtype_iterator_next(&it, &key, true); if (tok != WAGT_BEGIN_OBJECT) { @@ -10790,19 +10903,19 @@ agtype_value *alter_properties(agtype_value *original_properties, while (true) { - tok = agtype_iterator_next(&it, key, true); + tok = agtype_iterator_next(&it, &key, true); if (tok == WAGT_DONE || tok == WAGT_END_OBJECT) { break; } - agtype_iterator_next(&it, value, true); + agtype_iterator_next(&it, &value, true); parsed_agtype_value = push_agtype_value(&parse_state, WAGT_KEY, - key); + &key); parsed_agtype_value = push_agtype_value(&parse_state, WAGT_VALUE, - value); + &value); } parsed_agtype_value = push_agtype_value(&parse_state, WAGT_END_OBJECT, @@ -10838,6 +10951,7 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, { int total_args = PG_NARGS(); int actual_nargs = total_args - variadic_offset; + Oid argtype; /* Verify expected number of arguments */ if (actual_nargs != expected_nargs) @@ -10854,7 +10968,9 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, } /* Check if the argument is already an agtype */ - if (get_fn_expr_argtype(fcinfo->flinfo, variadic_offset) == AGTYPEOID) + argtype = get_cached_agtype_variadic_argtype(fcinfo, variadic_offset, + total_args); + if (argtype == AGTYPEOID) { agtype_container *agtc; @@ -10874,10 +10990,36 @@ agtype *get_one_agtype_from_variadic_args(FunctionCallInfo fcinfo, return agtype_result; } + if (!OidIsValid(argtype)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("could not determine data type for argument %d", + variadic_offset + 1))); + } + /* - * Not an agtype, need to convert. Fall through to use - * extract_variadic_args for type conversion handling. + * Not an agtype, but still a fixed non-variadic call. Convert this + * single datum directly instead of allocating extract_variadic_args() + * arrays just to read one value. */ + { + agtype_in_state state; + agt_type_category tcategory; + Oid outfuncoid; + + state.parse_state = NULL; + state.res = NULL; + + agtype_categorize_type(argtype, &tcategory, &outfuncoid); + datum_to_agtype(PG_GETARG_DATUM(variadic_offset), false, &state, + tcategory, outfuncoid, false); + agtype_result = agtype_value_to_agtype(state.res); + + pfree_agtype_in_state(&state); + + return agtype_result; + } } /* Standard path using extract_variadic_args */ @@ -11304,7 +11446,7 @@ Datum age_percentile_aggtransfn(PG_FUNCTION_ARGS) /* switch to the correct aggregate context */ old_mcxt = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt); /* create and initialize the state */ - pgastate = palloc0(sizeof(PercentileGroupAggState)); + pgastate = palloc(sizeof(PercentileGroupAggState)); pgastate->percentile = percentile; /* * Percentiles need to be calculated from a sorted set. We are only @@ -11485,9 +11627,12 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) { agtype_in_state *castate; int nargs; - Datum *args; - bool *nulls; - Oid *types; + Datum arg; + Datum *args = NULL; + bool is_null = true; + bool *nulls = NULL; + Oid type = InvalidOid; + Oid *types = NULL; MemoryContext old_mcxt; /* verify we are in an aggregate context */ @@ -11503,8 +11648,9 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0)) { /* create and initialize the state */ - castate = palloc0(sizeof(agtype_in_state)); - memset(castate, 0, sizeof(agtype_in_state)); + castate = palloc(sizeof(agtype_in_state)); + castate->parse_state = NULL; + castate->res = NULL; /* start the array */ castate->res = push_agtype_value(&castate->parse_state, @@ -11521,29 +11667,54 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) * Insert the arg into the array, unless it is null. Nulls are * skipped over. */ - if (PG_ARGISNULL(1)) + if (!get_fn_expr_variadic(fcinfo->flinfo)) { - nargs = 0; + nargs = PG_NARGS() - 1; + if (nargs == 1 && !PG_ARGISNULL(1)) + { + arg = PG_GETARG_DATUM(1); + type = get_cached_fn_expr_argtype(fcinfo, 1); + if (type == UNKNOWNOID && get_fn_expr_arg_stable(fcinfo->flinfo, 1)) + { + type = TEXTOID; + arg = CStringGetTextDatum(PG_GETARG_POINTER(1)); + } + is_null = false; + } } else { - nargs = extract_variadic_args(fcinfo, 1, true, &args, &types, &nulls); + if (PG_ARGISNULL(1)) + { + nargs = 0; + } + else + { + nargs = extract_variadic_args(fcinfo, 1, true, &args, &types, + &nulls); + if (nargs == 1) + { + arg = args[0]; + type = types[0]; + is_null = nulls[0]; + } + } } if (nargs == 1) { /* only add non null values */ - if (nulls[0] == false) + if (!is_null) { agtype_value *agtv_value = NULL; /* we need to check for agtype null and skip it, if found */ - if (types[0] == AGTYPEOID) + if (type == AGTYPEOID) { agtype *agt_arg; /* get the agtype argument */ - agt_arg = DATUM_GET_AGTYPE_P(args[0]); + agt_arg = DATUM_GET_AGTYPE_P(arg); /* get the scalar value */ if (AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) @@ -11555,7 +11726,7 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) /* skip the arg if agtype null */ if (agtv_value == NULL || agtv_value->type != AGTV_NULL) { - add_agtype(args[0], nulls[0], castate, types[0], false); + add_agtype(arg, is_null, castate, type, false); } } } @@ -11588,8 +11759,9 @@ Datum age_collect_aggfinalfn(PG_FUNCTION_ARGS) if (PG_ARGISNULL(0)) { /* create and initialize the state */ - castate = palloc0(sizeof(agtype_in_state)); - memset(castate, 0, sizeof(agtype_in_state)); + castate = palloc(sizeof(agtype_in_state)); + castate->parse_state = NULL; + castate->res = NULL; /* start the array */ castate->res = push_agtype_value(&castate->parse_state, WAGT_BEGIN_ARRAY, NULL); @@ -12427,8 +12599,11 @@ PG_FUNCTION_INFO_V1(age_range); Datum age_range(PG_FUNCTION_ARGS) { Datum *args = NULL; + Datum fast_args[3]; bool *nulls = NULL; + bool fast_nulls[3]; Oid *types = NULL; + Oid fast_types[3]; int nargs; int64 start_idx = 0; int64 end_idx = 0; @@ -12439,7 +12614,8 @@ Datum age_range(PG_FUNCTION_ARGS) int64 i = 0; /* get the arguments */ - nargs = extract_variadic_args(fcinfo, 0, false, &args, &types, &nulls); + nargs = get_variadic_args_fast(fcinfo, false, 3, &args, &types, &nulls, + fast_args, fast_types, fast_nulls); /* throw an error if the number of args is not the expected number */ if (nargs != 2 && nargs != 3) diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index f7f45b467..9b13cf6a9 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -40,19 +40,20 @@ static char *get_string_from_agtype_value(agtype_value *agtv, int *length); static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text); static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen); static agtype *delete_from_array(agtype *agt, agtype* indexes); +static bool parse_agtype_index_string(char *str, int len, long *lindex); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen) { int length = llen + rlen; - char *buffer = result->val.string.val; + char *buffer; Assert(llen >= 0 && rlen >= 0); check_string_length(length); buffer = palloc(length); - strncpy(buffer, lhs, llen); - strncpy(buffer + llen, rhs, rlen); + memcpy(buffer, lhs, llen); + memcpy(buffer + llen, rhs, rlen); result->type = AGTV_STRING; result->val.string.len = length; @@ -81,8 +82,8 @@ static char *get_string_from_agtype_value(agtype_value *agtv, int *length) if (is_decimal_needed(string)) { char *str = palloc(*length + 2); - strncpy(str, string, *length); - strncpy(str + *length, ".0", 2); + memcpy(str, string, *length); + memcpy(str + *length, ".0", 2); *length += 2; string = str; } @@ -109,6 +110,51 @@ static char *get_string_from_agtype_value(agtype_value *agtv, int *length) return NULL; } +static bool parse_agtype_index_string(char *str, int len, long *lindex) +{ + int i = 0; + bool negative = false; + int64 value = 0; + int64 limit; + + while (i < len && (str[i] == ' ' || str[i] == '\t' || + str[i] == '\n' || str[i] == '\r' || + str[i] == '\f')) + { + i++; + } + + if (i < len && (str[i] == '-' || str[i] == '+')) + { + negative = (str[i] == '-'); + i++; + } + + if (i >= len) + { + return false; + } + + limit = negative ? (int64) INT_MAX + 1 : INT_MAX; + + for (; i < len; i++) + { + if (str[i] < '0' || str[i] > '9') + { + return false; + } + + value = value * 10 + (str[i] - '0'); + if (value > limit) + { + return false; + } + } + + *lindex = negative ? -value : value; + return true; +} + Datum get_numeric_datum_from_agtype_value(agtype_value *agtv) { switch (agtv->type) @@ -2137,10 +2183,9 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) * extract the integer from the string, * if character other than a digit is found, return null */ - char* str = NULL; - lindex = strtol(cur_key->val.string.val, &str, 10); - - if (strcmp(str, "")) + if (!parse_agtype_index_string(cur_key->val.string.val, + cur_key->val.string.len, + &lindex)) { PG_RETURN_NULL(); } diff --git a/src/backend/utils/adt/agtype_parser.c b/src/backend/utils/adt/agtype_parser.c index 40fc8d8c5..623c14650 100644 --- a/src/backend/utils/adt/agtype_parser.c +++ b/src/backend/utils/adt/agtype_parser.c @@ -204,13 +204,16 @@ agtype_lex_context *make_agtype_lex_context(text *t, bool need_escapes) agtype_lex_context *make_agtype_lex_context_cstring_len(char *str, int len, bool need_escapes) { - agtype_lex_context *lex = palloc0(sizeof(agtype_lex_context)); + agtype_lex_context *lex = palloc(sizeof(agtype_lex_context)); lex->input = lex->token_terminator = lex->line_start = str; - lex->line_number = 1; lex->input_length = len; - if (need_escapes) - lex->strval = makeStringInfo(); + lex->token_start = NULL; + lex->prev_token_terminator = NULL; + lex->token_type = AGTYPE_TOKEN_INVALID; + lex->lex_level = 0; + lex->line_number = 1; + lex->strval = need_escapes ? makeStringInfo() : NULL; return lex; } diff --git a/src/backend/utils/adt/agtype_raw.c b/src/backend/utils/adt/agtype_raw.c index d8bad3d24..e012fa3f4 100644 --- a/src/backend/utils/adt/agtype_raw.c +++ b/src/backend/utils/adt/agtype_raw.c @@ -122,7 +122,7 @@ agtype_build_state *init_agtype_build_state(uint32 size, uint32 header_flag) int agtentry_len; agtype_build_state *bstate; - bstate = palloc0(sizeof(agtype_build_state)); + bstate = palloc(sizeof(agtype_build_state)); bstate->buffer = makeStringInfo(); bstate->a_offset = 0; bstate->i = 0; diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c index b39723413..8cb639978 100644 --- a/src/backend/utils/adt/agtype_util.c +++ b/src/backend/utils/adt/agtype_util.c @@ -1372,10 +1372,13 @@ static agtype_iterator *iterator_from_container(agtype_container *container, { agtype_iterator *it; - it = palloc0(sizeof(agtype_iterator)); + it = palloc(sizeof(agtype_iterator)); it->container = container; it->parent = parent; it->num_elems = AGTYPE_CONTAINER_SIZE(container); + it->curr_index = 0; + it->curr_data_offset = 0; + it->curr_value_offset = 0; /* Array starts just after header */ it->children = container->children; @@ -1395,6 +1398,7 @@ static agtype_iterator *iterator_from_container(agtype_container *container, case AGT_FOBJECT: it->data_proper = (char *)it->children + it->num_elems * sizeof(agtentry) * 2; + it->is_scalar = false; it->state = AGTI_OBJECT_START; break; diff --git a/src/backend/utils/ag_func.c b/src/backend/utils/ag_func.c index 17d9c9ca5..2f1b5ce99 100644 --- a/src/backend/utils/ag_func.c +++ b/src/backend/utils/ag_func.c @@ -51,6 +51,7 @@ typedef struct ag_func_cache_entry } ag_func_cache_entry; static HTAB *ag_func_oid_cache = NULL; +static HTAB *pg_func_oid_cache = NULL; static bool ag_func_oid_callback_registered = false; static void initialize_ag_func_oid_cache(void); @@ -63,7 +64,6 @@ bool is_oid_ag_func(Oid func_oid, const char *func_name) HeapTuple proctup; Form_pg_proc proc; Oid nspid; - const char *nspname; Assert(OidIsValid(func_oid)); Assert(func_name); @@ -80,9 +80,7 @@ bool is_oid_ag_func(Oid func_oid, const char *func_name) nspid = proc->pronamespace; ReleaseSysCache(proctup); - nspname = get_namespace_name_or_temp(nspid); - Assert(nspname); - return (strcmp(nspname, "ag_catalog") == 0); + return nspid == ag_catalog_namespace_id(); } /* gets the function OID that matches with func_name and argument types */ @@ -161,6 +159,8 @@ static void initialize_ag_func_oid_cache(void) ag_func_oid_cache = hash_create("ag function oid cache", 32, &hash_ctl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + pg_func_oid_cache = hash_create("pg function oid cache", 16, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); if (!ag_func_oid_callback_registered) { @@ -181,25 +181,44 @@ static void invalidate_ag_func_oid_cache(Datum arg, int cache_id, hash_destroy(ag_func_oid_cache); ag_func_oid_cache = NULL; } + + if (pg_func_oid_cache != NULL) + { + hash_destroy(pg_func_oid_cache); + pg_func_oid_cache = NULL; + } } Oid get_pg_func_oid(const char *func_name, const int nargs, ...) { - Oid oids[FUNC_MAX_ARGS]; + ag_func_cache_key key; + ag_func_cache_entry *entry; va_list ap; int i; oidvector *arg_types; Oid func_oid; + bool found; Assert(func_name); Assert(nargs >= 0 && nargs <= FUNC_MAX_ARGS); + initialize_ag_func_oid_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.name, func_name); + key.nargs = nargs; + va_start(ap, nargs); for (i = 0; i < nargs; i++) - oids[i] = va_arg(ap, Oid); + key.args[i] = va_arg(ap, Oid); va_end(ap); - arg_types = buildoidvector(oids, nargs); + entry = hash_search(pg_func_oid_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->func_oid; + } + + arg_types = buildoidvector(key.args, nargs); func_oid = GetSysCacheOid3(PROCNAMEARGSNSP, Anum_pg_proc_oid, CStringGetDatum(func_name), @@ -211,5 +230,16 @@ Oid get_pg_func_oid(const char *func_name, const int nargs, ...) errdetail_internal("%s(%d)", func_name, nargs))); } - return func_oid; + if (pg_func_oid_cache == NULL) + { + initialize_ag_func_oid_cache(); + } + + entry = hash_search(pg_func_oid_cache, &key, HASH_ENTER, &found); + if (!found) + { + entry->func_oid = func_oid; + } + + return entry->func_oid; } diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 7631223d4..e405b083b 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -386,7 +386,7 @@ graph_cache_data *search_graph_name_cache_cached(const char *name) if (cached_graphs[slot] != NULL) { namestrcpy(&cached_names[slot], name); - cached_generations[slot] = get_graph_cache_generation(); + cached_generations[slot] = current_generation; } return cached_graphs[slot]; @@ -433,7 +433,7 @@ graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) if (cached_graphs[slot] != NULL) { cached_namespaces[slot] = namespace; - cached_generations[slot] = get_graph_cache_generation(); + cached_generations[slot] = current_generation; } return cached_graphs[slot]; @@ -1025,7 +1025,7 @@ label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) cached_graphs[slot] = graph; cached_ids[slot] = id; - cached_generations[slot] = get_label_cache_generation(); + cached_generations[slot] = current_generation; cached_labels[slot] = label; } @@ -1080,7 +1080,7 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, namestrcpy(&cached_names[slot], name); cached_graphs[slot] = graph; - cached_generations[slot] = get_label_cache_generation(); + cached_generations[slot] = current_generation; cached_labels[slot] = label; } @@ -1304,7 +1304,7 @@ label_cache_data *search_label_relation_cache_cached(Oid relation) } cached_relations[slot] = relation; - cached_generations[slot] = get_label_cache_generation(); + cached_generations[slot] = current_generation; cached_labels[slot] = label; } @@ -1430,8 +1430,7 @@ Oid get_label_seq_relation_cached(const label_cache_data *label_cache, namestrcpy(&label_seq_relation_cached_names[slot], seq_name); label_seq_relation_cached_namespaces[slot] = namespace; - label_seq_relation_cached_generations[slot] = - label_seq_relation_cache_generation; + label_seq_relation_cached_generations[slot] = current_generation; label_seq_relation_cached_relations[slot] = seq_relid; return seq_relid; diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index 0b6d24f32..f90e682eb 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -46,7 +46,7 @@ estate->es_snapshot->curcid--; #define DELETE_VERTEX_HTAB_NAME "delete_vertex_htab" -#define DELETE_VERTEX_HTAB_SIZE 1000000 +#define DELETE_VERTEX_HTAB_INITIAL_SIZE 1024 typedef struct cypher_create_custom_scan_state { From 41c7aa272dfb923398989a87bb44c26a103ad71e Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:38 +0900 Subject: [PATCH 08/15] Trim parser, load, and text conversion work Reduce transient allocation and repeated string work in parser function lookups, CSV load helpers, graph OID microcaches, VLE range handling, and text scalar conversion paths. The change frees short-lived lookup data after syscache probes, scans trimmed CSV fields once, avoids detoast and cstring copies for length-only checks, and uses direct text varlena data where possible. It also keeps related DML hash, slot, and planner cheapest-path improvements with the same performance pass. --- src/backend/executor/cypher_delete.c | 22 +- src/backend/executor/cypher_merge.c | 110 ++++++- src/backend/executor/cypher_set.c | 11 +- src/backend/optimizer/cypher_pathnode.c | 5 + src/backend/parser/cypher_clause.c | 106 ++++--- src/backend/parser/cypher_expr.c | 118 ++++--- src/backend/utils/adt/age_global_graph.c | 13 +- src/backend/utils/adt/age_vle.c | 49 ++- src/backend/utils/adt/agtype.c | 373 +++++++++-------------- src/backend/utils/ag_func.c | 4 + src/backend/utils/cache/ag_cache.c | 4 +- src/backend/utils/load/ag_load_edges.c | 119 ++++++-- src/backend/utils/load/age_load.c | 118 +++---- src/include/executor/cypher_utils.h | 6 +- 14 files changed, 620 insertions(+), 438 deletions(-) diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 87a23e71f..763b128d7 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -890,15 +890,22 @@ static void check_for_connected_edges(CustomScanState *node) else { scan_desc = table_beginscan(rel, estate->es_snapshot, 0, NULL); - slot = ExecInitExtraTupleSlot( - estate, RelationGetDescr(rel), - &TTSOpsHeapTuple); + slot = idx_entry->slot; + if (slot == NULL) + { + slot = ExecInitExtraTupleSlot(estate, RelationGetDescr(rel), + &TTSOpsHeapTuple); + idx_entry->slot = slot; + } + else + { + ExecClearTuple(slot); + } /* for each row */ while (true) { graphid startid; - graphid endid; bool isNull; bool found_startid = false; bool found_endid = false; @@ -915,14 +922,16 @@ static void check_for_connected_edges(CustomScanState *node) startid = GRAPHID_GET_DATUM(slot_getattr( slot, Anum_ag_label_edge_table_start_id, &isNull)); - endid = GRAPHID_GET_DATUM( - slot_getattr(slot, Anum_ag_label_edge_table_end_id, &isNull)); hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND, &found_startid); if (!found_startid) { + graphid endid; + + endid = GRAPHID_GET_DATUM(slot_getattr( + slot, Anum_ag_label_edge_table_end_id, &isNull)); hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND, &found_endid); } @@ -983,6 +992,7 @@ static void check_for_connected_edges(CustomScanState *node) } } + ExecClearTuple(slot); table_endscan(scan_desc); } diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index aea995d58..29b65dd5e 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -59,6 +59,7 @@ typedef struct created_path { struct created_path *next; /* next link in linked list of path_entrys */ struct path_entry **entry; /* path_entry array for this link */ + uint32 path_hash; /* cheap precheck before full path compare */ } created_path; static void begin_cypher_merge(CustomScanState *node, EState *estate, @@ -92,10 +93,12 @@ const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME, NULL, NULL, NULL, NULL}; static path_entry **prebuild_path(CustomScanState *node); +static uint32 hash_path_entries(path_entry **path_array, int path_length); static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length); static path_entry **find_duplicate_path(CustomScanState *node, - path_entry **path_array); + path_entry **path_array, + uint32 path_hash); static void free_path_entry_array(path_entry **path_array, int length); /* @@ -361,7 +364,8 @@ static void process_simple_merge(CustomScanState *node) if (css->on_create_set_info) { mark_scan_slot_valid(econtext->ecxt_scantuple); - apply_update_list(&css->css, css->on_create_set_info); + apply_update_list(&css->css, css->on_create_set_info, + css->on_create_set_item_count); } } else @@ -374,7 +378,8 @@ static void process_simple_merge(CustomScanState *node) econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info); + apply_update_list(&css->css, css->on_match_set_info, + css->on_match_set_item_count); } } } @@ -429,6 +434,41 @@ static void free_path_entry_array(path_entry **path_array, int length) } } +static uint32 hash_path_entries(path_entry **path_array, int path_length) +{ + uint32 hash = 2166136261U; + int i; + + for (i = 0; i < path_length; i++) + { + path_entry *entry = path_array[i]; + +#define PATH_HASH_COMBINE(v) \ + do { \ + hash ^= (uint32)(v); \ + hash *= 16777619U; \ + } while (0) + + PATH_HASH_COMBINE(entry->actual); + if (entry->actual) + { + PATH_HASH_COMBINE(entry->id); + PATH_HASH_COMBINE(entry->id >> 32); + continue; + } + + PATH_HASH_COMBINE(entry->label); + PATH_HASH_COMBINE(entry->direction); + PATH_HASH_COMBINE(entry->prop_isNull); + if (!entry->prop_isNull) + PATH_HASH_COMBINE(entry->dih); + +#undef PATH_HASH_COMBINE + } + + return hash; +} + /* * Helper function to prebuild a path. The user needs to free the returned * path_entry when done. @@ -470,7 +510,7 @@ static path_entry **prebuild_path(CustomScanState *node) entry->prop = ExecEvalExprSwitchContext(node->prop_expr_state, econtext, &isNull); entry->prop_isNull = isNull; - entry->dih = datum_image_hash(entry->prop, false, -1); + entry->dih = isNull ? 0 : datum_image_hash(entry->prop, false, -1); } /* otherwise, it is */ else @@ -577,6 +617,16 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length) } /* are the properties datum hashes the same */ + if (lhs[i]->prop_isNull != rhs[i]->prop_isNull) + { + return false; + } + + if (lhs[i]->prop_isNull) + { + continue; + } + if (lhs[i]->dih != rhs[i]->dih) { return false; @@ -595,7 +645,8 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length) /* helper function to find a duplicate path in the created_paths_list */ static path_entry **find_duplicate_path(CustomScanState *node, - path_entry **path_array) + path_entry **path_array, + uint32 path_hash) { cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; @@ -615,7 +666,8 @@ static path_entry **find_duplicate_path(CustomScanState *node, while (curr_path != NULL) { /* if we have found the entry, return it */ - if (compare_2_paths(path_array, curr_path->entry, + if (curr_path->path_hash == path_hash && + compare_2_paths(path_array, curr_path->entry, css->path_length)) { return curr_path->entry; @@ -715,22 +767,28 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; + uint32 path_hash; prebuilt_path_array = prebuild_path(node); + path_hash = hash_path_entries(prebuilt_path_array, + css->path_length); found_path_array = - find_duplicate_path(node, prebuilt_path_array); + find_duplicate_path(node, prebuilt_path_array, + path_hash); if (found_path_array) { free_path_entry_array(prebuilt_path_array, css->path_length); + pfree(prebuilt_path_array); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) apply_update_list(&css->css, - css->on_match_set_info); + css->on_match_set_info, + css->on_match_set_item_count); } else { @@ -738,6 +796,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) new_path->next = css->created_paths_list; new_path->entry = prebuilt_path_array; + new_path->path_hash = path_hash; css->created_paths_list = new_path; process_path(css, prebuilt_path_array, true); @@ -746,14 +805,16 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON CREATE SET: path was just created */ if (css->on_create_set_info) apply_update_list(&css->css, - css->on_create_set_info); + css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_update_list(&css->css, css->on_match_set_info, + css->on_match_set_item_count); } /* Project the result and save a copy */ @@ -818,21 +879,27 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { path_entry **prebuilt_path_array = NULL; path_entry **found_path_array = NULL; + uint32 path_hash; prebuilt_path_array = prebuild_path(node); + path_hash = hash_path_entries(prebuilt_path_array, + css->path_length); found_path_array = find_duplicate_path(node, - prebuilt_path_array); + prebuilt_path_array, + path_hash); if (found_path_array) { free_path_entry_array(prebuilt_path_array, css->path_length); + pfree(prebuilt_path_array); process_path(css, found_path_array, false); /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_update_list(&css->css, css->on_match_set_info, + css->on_match_set_item_count); } else { @@ -840,6 +907,7 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) new_path->next = css->created_paths_list; new_path->entry = prebuilt_path_array; + new_path->path_hash = path_hash; css->created_paths_list = new_path; process_path(css, prebuilt_path_array, true); @@ -847,14 +915,16 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info); + apply_update_list(&css->css, css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info); + apply_update_list(&css->css, css->on_match_set_info, + css->on_match_set_item_count); } } while (true); @@ -933,7 +1003,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info); + apply_update_list(&css->css, css->on_match_set_info, + css->on_match_set_item_count); } econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); @@ -1011,7 +1082,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info); + apply_update_list(&css->css, css->on_create_set_info, + css->on_create_set_item_count); /* mark the create_new_path flag to true. */ css->created_new_path = true; @@ -1177,6 +1249,12 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) cypher_css->graph_oid = merge_information->graph_oid; cypher_css->on_match_set_info = merge_information->on_match_set_info; cypher_css->on_create_set_info = merge_information->on_create_set_info; + cypher_css->on_match_set_item_count = + cypher_css->on_match_set_info == NULL ? 0 : + list_length(cypher_css->on_match_set_info->set_items); + cypher_css->on_create_set_item_count = + cypher_css->on_create_set_info == NULL ? 0 : + list_length(cypher_css->on_create_set_info->set_items); cypher_css->css.ss.ps.type = T_CustomScanState; cypher_css->css.methods = &cypher_merge_exec_methods; diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 22d78e490..4ccd4222d 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -109,6 +109,8 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } + css->set_item_count = list_length(css->set_list->set_items); + foreach(lc, css->set_list->set_items) { cypher_update_item *item = (cypher_update_item *)lfirst(lc); @@ -468,7 +470,8 @@ static void update_all_paths(CustomScanState *node, graphid id, * cypher_update_information describing which properties to set. */ void apply_update_list(CustomScanState *node, - cypher_update_information *set_info) + cypher_update_information *set_info, + int num_set_items) { ExprContext *econtext = node->ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; @@ -476,7 +479,6 @@ void apply_update_list(CustomScanState *node, EState *estate = node->ss.ps.state; int *luindex = NULL; int lidx = 0; - int num_set_items; HTAB *qual_cache = NULL; HTAB *index_cache = NULL; HTAB *result_rel_info_cache = NULL; @@ -536,7 +538,8 @@ void apply_update_list(CustomScanState *node, } } - num_set_items = list_length(set_info->set_items); + if (num_set_items <= 0) + num_set_items = list_length(set_info->set_items); /* * Iterate through the SET items list and store the loop index of each @@ -1008,7 +1011,7 @@ static void process_update_list(CustomScanState *node) { cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - apply_update_list(node, css->set_list); + apply_update_list(node, css->set_list, css->set_item_count); } static TupleTableSlot *exec_cypher_set(CustomScanState *node) diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 78c729425..4ae03d19d 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -217,6 +217,11 @@ static Path *select_best_child_path(RelOptInfo *rel) Path *best_child = NULL; ListCell *lc; + if (rel->cheapest_total_path != NULL) + { + return rel->cheapest_total_path; + } + foreach (lc, rel->pathlist) { Path *child = (Path *)lfirst(lc); diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 43b96fdd2..3f783e65e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -5511,38 +5511,50 @@ static char *get_accessor_function_name(enum transform_entity_type type, { if (type == ENT_VERTEX) { - /* id */ - if (!strcmp(AG_VERTEX_COLNAME_ID, name)) + switch (name[0]) { - return AG_VERTEX_ACCESS_FUNCTION_ID; - } - /* props */ - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - { - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + case 'i': + if (strcmp(AG_VERTEX_COLNAME_ID, name) == 0) + { + return AG_VERTEX_ACCESS_FUNCTION_ID; + } + break; + case 'p': + if (strcmp(AG_VERTEX_COLNAME_PROPERTIES, name) == 0) + { + return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + } + break; } } if (type == ENT_EDGE) { - /* id */ - if (!strcmp(AG_EDGE_COLNAME_ID, name)) - { - return AG_EDGE_ACCESS_FUNCTION_ID; - } - /* start id */ - else if (!strcmp(AG_EDGE_COLNAME_START_ID, name)) - { - return AG_EDGE_ACCESS_FUNCTION_START_ID; - } - /* end id */ - else if (!strcmp(AG_EDGE_COLNAME_END_ID, name)) + switch (name[0]) { - return AG_EDGE_ACCESS_FUNCTION_END_ID; - } - /* props */ - else if (!strcmp(AG_VERTEX_COLNAME_PROPERTIES, name)) - { - return AG_VERTEX_ACCESS_FUNCTION_PROPERTIES; + case 'e': + if (strcmp(AG_EDGE_COLNAME_END_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_END_ID; + } + break; + case 'i': + if (strcmp(AG_EDGE_COLNAME_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_ID; + } + break; + case 'p': + if (strcmp(AG_EDGE_COLNAME_PROPERTIES, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_PROPERTIES; + } + break; + case 's': + if (strcmp(AG_EDGE_COLNAME_START_ID, name) == 0) + { + return AG_EDGE_ACCESS_FUNCTION_START_ID; + } + break; } } @@ -8365,21 +8377,35 @@ static Oid get_clause_function_oid(const char *function_name) get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - if (strcmp(function_name, CREATE_CLAUSE_FUNCTION_NAME) == 0) - { - return create_clause_func_oid; - } - if (strcmp(function_name, SET_CLAUSE_FUNCTION_NAME) == 0) - { - return set_clause_func_oid; - } - if (strcmp(function_name, DELETE_CLAUSE_FUNCTION_NAME) == 0) - { - return delete_clause_func_oid; - } - if (strcmp(function_name, MERGE_CLAUSE_FUNCTION_NAME) == 0) + if (strncmp(function_name, "_cypher_", sizeof("_cypher_") - 1) == 0) { - return merge_clause_func_oid; + switch (function_name[sizeof("_cypher_") - 1]) + { + case 'c': + if (strcmp(function_name, CREATE_CLAUSE_FUNCTION_NAME) == 0) + { + return create_clause_func_oid; + } + break; + case 'd': + if (strcmp(function_name, DELETE_CLAUSE_FUNCTION_NAME) == 0) + { + return delete_clause_func_oid; + } + break; + case 'm': + if (strcmp(function_name, MERGE_CLAUSE_FUNCTION_NAME) == 0) + { + return merge_clause_func_oid; + } + break; + case 's': + if (strcmp(function_name, SET_CLAUSE_FUNCTION_NAME) == 0) + { + return set_clause_func_oid; + } + break; + } } return get_ag_func_oid(function_name, 1, INTERNALOID); diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index e36e13f65..c585eaed6 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -142,7 +142,7 @@ static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, FuncCall *fn, List *targs, Form_pg_proc procform, - char *extension); + const char *extension); static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, Form_pg_proc procform, List *fargs, @@ -150,9 +150,10 @@ static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, FuncExpr *fexpr); static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); -static char *get_mapped_extension(Oid func_oid); +static const char *get_mapped_extension(Oid func_oid); static bool function_belongs_to_extension(Oid func_oid, const char *extension); -static bool is_extension_external(char *extension); +static bool is_extension_external(const char *extension); +static bool function_needs_graph_name_argument(const char *name); static char *construct_age_function_name(char *funcname); static void initialize_function_caches(void); static void invalidate_function_caches(Datum arg, int cache_id, @@ -1753,7 +1754,7 @@ static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, FuncCall *fn, List *targs, Form_pg_proc procform, - char *extension) + const char *extension) { ParseState *pstate = &cpstate->pstate; FuncExpr *fexpr = NULL; @@ -1772,6 +1773,7 @@ static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, fexpr = (FuncExpr *)ParseFuncOrColumn(pstate, fn->funcname, targs, last_srf, fn, false, fn->location); + pfree(procform); /* * This will cast TEXT output to AGTYPE. It will error out if this is @@ -1877,7 +1879,8 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) Form_pg_proc result = NULL; int nargs; int i = 0; - List *asp; + List *asp = NIL; + bool asp_fetched = false; bool found = false; char *funcname = (((String*)linitial(fn->funcname))->sval); @@ -1890,7 +1893,6 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) return NULL; } - asp = fetch_search_path(false); nargs = list_length(fn->args); /* iterate through them and verify that they are in the search path */ @@ -1908,6 +1910,12 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) nargs == procform->pronargs && fn->func_variadic == procform->provariadic) { + if (!asp_fetched) + { + asp = fetch_search_path(false); + asp_fetched = true; + } + if (list_member_oid(asp, procform->pronamespace) && !isTempNamespace(procform->pronamespace)) { @@ -1944,7 +1952,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) return result; } -static char *get_mapped_extension(Oid func_oid) +static const char *get_mapped_extension(Oid func_oid) { Oid extension_oid; char *extension = NULL; @@ -1960,7 +1968,7 @@ static char *get_mapped_extension(Oid func_oid) { return NULL; } - return pstrdup(NameStr(entry->extension)); + return NameStr(entry->extension); } extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); @@ -1982,7 +1990,12 @@ static char *get_mapped_extension(Oid func_oid) } } - return extension; + if (extension != NULL) + { + pfree(extension); + } + + return entry->has_extension ? NameStr(entry->extension) : NULL; } static bool function_belongs_to_extension(Oid func_oid, const char *extension) @@ -1990,6 +2003,7 @@ static bool function_belongs_to_extension(Oid func_oid, const char *extension) Oid extension_oid; char *extension_name = NULL; function_extension_cache_entry *entry; + bool belongs; bool found; initialize_function_extension_cache(); @@ -2020,16 +2034,39 @@ static bool function_belongs_to_extension(Oid func_oid, const char *extension) } } - return extension_name != NULL && - pg_strcasecmp(extension_name, extension) == 0; + belongs = (extension_name != NULL && + pg_strcasecmp(extension_name, extension) == 0); + + if (extension_name != NULL) + { + pfree(extension_name); + } + + return belongs; } -static bool is_extension_external(char *extension) +static bool is_extension_external(const char *extension) { return ((extension != NULL) && (pg_strcasecmp(extension, "age") != 0)); } +static bool function_needs_graph_name_argument(const char *name) +{ + switch (name[0]) + { + case 'e': + return strcmp(name, "endNode") == 0; + case 's': + return strcmp(name, "startNode") == 0; + case 'v': + return (strcmp(name, "vle") == 0 || + strcmp(name, "vertex_stats") == 0); + default: + return false; + } +} + /* Returns age_ prefiexed lower case function name */ static char *construct_age_function_name(char *funcname) { @@ -2069,22 +2106,18 @@ static bool function_exists(char *funcname, char *extension) bool cache_found; int i = 0; + initialize_function_exists_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.funcname, funcname); if (extension != NULL) { - initialize_function_exists_cache(); - MemSet(&key, 0, sizeof(key)); - namestrcpy(&key.funcname, funcname); namestrcpy(&key.extension, extension); - - entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); - if (entry != NULL) - { - return entry->exists; - } } - else + + entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); + if (entry != NULL) { - entry = NULL; + return entry->exists; } /* get a list of matching functions */ @@ -2093,21 +2126,17 @@ static bool function_exists(char *funcname, char *extension) if (catlist->n_members == 0) { ReleaseSysCacheList(catlist); - if (extension != NULL) - { - if (function_exists_cache == NULL) - { - initialize_function_exists_cache(); - } - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - entry->exists = false; - } + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = false; return false; } else if (extension == NULL) { ReleaseSysCacheList(catlist); + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = true; return true; } @@ -2126,16 +2155,9 @@ static bool function_exists(char *funcname, char *extension) /* we need to release the cache list */ ReleaseSysCacheList(catlist); - if (extension != NULL) - { - if (function_exists_cache == NULL) - { - initialize_function_exists_cache(); - } - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - entry->exists = found; - } + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = found; return found; } @@ -2464,10 +2486,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * is not empty. Then prepend the graph name if necessary. */ if ((targs != NIL) && - (strcmp("startNode", name) == 0 || - strcmp("endNode", name) == 0 || - strcmp("vle", name) == 0 || - strcmp("vertex_stats", name) == 0)) + function_needs_graph_name_argument(name)) { char *graph_name = cpstate->graph_name; Datum d = string_to_agtype(graph_name); @@ -2484,7 +2503,9 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) else { Form_pg_proc procform = get_procform(fn, false); - char *extension; + const char *extension; + + pfree(ag_name); if (procform == NULL) { @@ -2514,6 +2535,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) */ else { + pfree(procform); fname = fn->funcname; } } diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index d6728f4fc..fae9acedc 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -1127,10 +1127,10 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && graph_name_len < NAMEDATALEN && strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && - NameStr(cached_graph_name)[graph_name_len] == '\0' && - cached_generation == current_generation) + NameStr(cached_graph_name)[graph_name_len] == '\0') { return cached_graph_oid; } @@ -1150,7 +1150,14 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name_cstr); + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } cached_generation = current_generation; } if (free_graph_name) diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 5eb06425b..70dfef839 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -644,6 +644,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, agtype_value *agtv_temp = NULL; agtype_value *agtv_object = NULL; agtype *agt_edge_property_constraint = NULL; + agtype *agt_arg = NULL; Datum d_edge_property_constraint = 0; char *graph_name = NULL; Oid graph_oid = InvalidOid; @@ -876,29 +877,46 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } /* get the left range index */ - if (PG_ARGISNULL(4) || is_agtype_null(AG_GET_ARG_AGTYPE_P(4))) + if (PG_ARGISNULL(4)) { vlelctx->lidx = 1; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(4), - AGTV_INTEGER, true); - vlelctx->lidx = agtv_temp->val.int_value; + agt_arg = AG_GET_ARG_AGTYPE_P(4); + if (is_agtype_null(agt_arg)) + { + vlelctx->lidx = 1; + } + else + { + agtv_temp = get_agtype_value("age_vle", agt_arg, AGTV_INTEGER, + true); + vlelctx->lidx = agtv_temp->val.int_value; + } } /* get the right range index. NULL means infinite */ - if (PG_ARGISNULL(5) || is_agtype_null(AG_GET_ARG_AGTYPE_P(5))) + if (PG_ARGISNULL(5)) { vlelctx->uidx_infinite = true; vlelctx->uidx = 0; } else { - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(5), - AGTV_INTEGER, true); - vlelctx->uidx = agtv_temp->val.int_value; - vlelctx->uidx_infinite = false; + agt_arg = AG_GET_ARG_AGTYPE_P(5); + if (is_agtype_null(agt_arg)) + { + vlelctx->uidx_infinite = true; + vlelctx->uidx = 0; + } + else + { + agtv_temp = get_agtype_value("age_vle", agt_arg, AGTV_INTEGER, + true); + vlelctx->uidx = agtv_temp->val.int_value; + vlelctx->uidx_infinite = false; + } } /* get edge direction */ agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(6), @@ -947,10 +965,10 @@ static Oid get_cached_vle_graph_oid(const char *graph_name, bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && graph_name_len < NAMEDATALEN && strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && - NameStr(cached_graph_name)[graph_name_len] == '\0' && - cached_generation == current_generation) + NameStr(cached_graph_name)[graph_name_len] == '\0') { return cached_graph_oid; } @@ -970,7 +988,14 @@ static Oid get_cached_vle_graph_oid(const char *graph_name, cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name_cstr); + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } cached_generation = current_generation; } if (free_graph_name) diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 50e094101..463c7446c 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -142,7 +142,7 @@ static void agtype_in_object_field_start(void *pstate, char *fname, bool isnull); static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, bool extend); -static void escape_agtype(StringInfo buf, const char *str); +static void escape_agtype(StringInfo buf, const char *str, int len); bool is_decimal_needed(char *numstr); static void agtype_in_scalar(void *pstate, char *token, agtype_token_type tokentype, @@ -231,6 +231,7 @@ agtype_value *agtype_composite_to_agtype_value_binary(agtype *a); static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr); static bool agtype_string_contains(char *haystack, int haystack_len, char *needle, int needle_len); +static agtype *text_to_agtype_string_value(text *txt); void *repalloc_check(void *ptr, size_t len) @@ -1109,8 +1110,8 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, appendBinaryStringInfo(out, "null", 4); break; case AGTV_STRING: - escape_agtype(out, pnstrdup(scalar_val->val.string.val, - scalar_val->val.string.len)); + escape_agtype(out, scalar_val->val.string.val, + scalar_val->val.string.len); break; case AGTV_NUMERIC: appendStringInfoString( @@ -1188,12 +1189,13 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, /* * Produce an agtype string literal, properly escaping characters in the text. */ -static void escape_agtype(StringInfo buf, const char *str) +static void escape_agtype(StringInfo buf, const char *str, int len) { const char *p; + const char *end = str + len; appendStringInfoCharMacro(buf, '"'); - for (p = str; *p; p++) + for (p = str; p < end; p++) { switch (*p) { @@ -2276,6 +2278,17 @@ agtype_value *string_to_agtype_value(char *s) return agtv; } +static agtype *text_to_agtype_string_value(text *txt) +{ + agtype_value agtv; + + agtv.type = AGTV_STRING; + agtv.val.string.len = check_string_length(VARSIZE_ANY_EXHDR(txt)); + agtv.val.string.val = VARDATA_ANY(txt); + + return agtype_value_to_agtype(&agtv); +} + /* helper function to create an agtype_value integer from an integer */ agtype_value *integer_to_agtype_value(int64 int_value) { @@ -3524,10 +3537,7 @@ PG_FUNCTION_INFO_V1(text_to_agtype); Datum text_to_agtype(PG_FUNCTION_ARGS) { agtype *result = NULL; - agtype_value agtv; text *text_value = NULL; - char *string = NULL; - int len = 0; if (PG_ARGISNULL(0)) { @@ -3536,21 +3546,7 @@ Datum text_to_agtype(PG_FUNCTION_ARGS) /* get the text value */ text_value = PG_GETARG_TEXT_PP(0); - /* convert it to a string */ - string = text_to_cstring(text_value); - /* get the length */ - len = strlen(string); - - /* create a temporary agtype string */ - agtv.type = AGTV_STRING; - agtv.val.string.len = len; - agtv.val.string.val = pstrdup(string); - - /* free the string */ - pfree_if_not_null(string); - - /* convert to agtype */ - result = agtype_value_to_agtype(&agtv); + result = text_to_agtype_string_value(text_value); /* free the input arg if necessary */ PG_FREE_IF_COPY(text_value, 0); @@ -4865,6 +4861,7 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) agtype_value *agtv_arg, agtv_item, agtv_elem; uint32 array_size = 0; bool result = false; + bool is_vpc; uint32 i = 0; /* return null if the array is null */ @@ -4881,55 +4878,13 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("object of IN must be a list"))); } + is_vpc = AGT_ROOT_IS_VPC(agt_arg); + /* If we have vpc as arg, get the agtype_value AGTV_ARRAY of edges */ - if (AGT_ROOT_IS_VPC(agt_arg)) + if (is_vpc) { agtv_arg = agtv_materialize_vle_edges(agt_arg); array_size = agtv_arg->val.array.num_elems; - - /* return null if the item to find is null */ - if (PG_ARGISNULL(1)) - { - PG_RETURN_NULL(); - } - /* get the item to search for */ - agt_item = AG_GET_ARG_AGTYPE_P(1); - - /* init item iterator */ - it_item = agtype_iterator_init(&agt_item->root); - - /* get value of item */ - agtype_iterator_next(&it_item, &agtv_item, false); - if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) - { - agtype_iterator_next(&it_item, &agtv_item, false); - /* check for AGTYPE NULL */ - if (agtv_item.type == AGTV_NULL) - { - PG_RETURN_NULL(); - } - } - - /* iterate through the array, but stop if we find it */ - for (i = 0; i < array_size && !result; i++) - { - agtv_elem = agtv_arg->val.array.elems[i]; - - /* if both are containers, compare containers */ - if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) - { - result = (compare_agtype_containers_orderability( - &agt_item->root, agtv_elem.val.binary.data) == 0); - } - /* if both are scalars and of the same type, compare scalars */ - else if (IS_A_AGTYPE_SCALAR(&agtv_item) && - IS_A_AGTYPE_SCALAR(&agtv_elem) && - agtv_item.type == agtv_elem.type) - { - result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == - 0); - } - } } /* Else we need to iterate agtype_container */ else @@ -4953,49 +4908,56 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) } array_size = AGT_ROOT_COUNT(agt_arg); + } - /* return null if the item to find is null */ - if (PG_ARGISNULL(1)) - { - PG_RETURN_NULL(); - } - /* get the item to search for */ - agt_item = AG_GET_ARG_AGTYPE_P(1); + /* return null if the item to find is null */ + if (PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + /* get the item to search for */ + agt_item = AG_GET_ARG_AGTYPE_P(1); - /* init item iterator */ - it_item = agtype_iterator_init(&agt_item->root); + /* init item iterator */ + it_item = agtype_iterator_init(&agt_item->root); - /* get value of item */ + /* get value of item */ + agtype_iterator_next(&it_item, &agtv_item, false); + if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) + { agtype_iterator_next(&it_item, &agtv_item, false); - if (agtv_item.type == AGTV_ARRAY && agtv_item.val.array.raw_scalar) + /* check for AGTYPE NULL */ + if (agtv_item.type == AGTV_NULL) { - agtype_iterator_next(&it_item, &agtv_item, false); - /* check for AGTYPE NULL */ - if (agtv_item.type == AGTV_NULL) - { - PG_RETURN_NULL(); - } + PG_RETURN_NULL(); } + } - /* iterate through the array, but stop if we find it */ - for (i = 0; i < array_size && !result; i++) + /* iterate through the array, but stop if we find it */ + for (i = 0; i < array_size && !result; i++) + { + if (is_vpc) + { + agtv_elem = agtv_arg->val.array.elems[i]; + } + else { - /* get next element */ agtype_iterator_next(&it_array, &agtv_elem, true); - /* if both are containers, compare containers */ - if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) - { - result = (compare_agtype_containers_orderability( - &agt_item->root, agtv_elem.val.binary.data) == 0); - } - /* if both are scalars and of the same type, compare scalars */ - else if (IS_A_AGTYPE_SCALAR(&agtv_item) && - IS_A_AGTYPE_SCALAR(&agtv_elem) && - agtv_item.type == agtv_elem.type) - { - result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == - 0); - } + } + + /* if both are containers, compare containers */ + if (!IS_A_AGTYPE_SCALAR(&agtv_item) && !IS_A_AGTYPE_SCALAR(&agtv_elem)) + { + result = (compare_agtype_containers_orderability( + &agt_item->root, agtv_elem.val.binary.data) == 0); + } + /* if both are scalars and of the same type, compare scalars */ + else if (IS_A_AGTYPE_SCALAR(&agtv_item) && + IS_A_AGTYPE_SCALAR(&agtv_elem) && + agtv_item.type == agtv_elem.type) + { + result = (compare_agtype_scalar_values(&agtv_item, &agtv_elem) == + 0); } } @@ -5940,10 +5902,10 @@ static Oid get_cached_graph_oid_for_name(const char *graph_name, bool free_graph_name = false; if (OidIsValid(cached_graph_oid) && + cached_generation == current_generation && graph_name_len < NAMEDATALEN && strncmp(NameStr(cached_graph_name), graph_name, graph_name_len) == 0 && - NameStr(cached_graph_name)[graph_name_len] == '\0' && - cached_generation == current_generation) + NameStr(cached_graph_name)[graph_name_len] == '\0') { return cached_graph_oid; } @@ -5963,7 +5925,14 @@ static Oid get_cached_graph_oid_for_name(const char *graph_name, cached_graph_oid = get_graph_oid(graph_name_cstr); if (OidIsValid(cached_graph_oid)) { - namestrcpy(&cached_graph_name, graph_name_cstr); + if (graph_name_len < NAMEDATALEN) + { + cached_graph_name = graph_name_buf; + } + else + { + namestrcpy(&cached_graph_name, graph_name_cstr); + } cached_generation = current_generation; } if (free_graph_name) @@ -6606,16 +6575,28 @@ Datum age_toboolean(PG_FUNCTION_ARGS) else if (type == CSTRINGOID || type == TEXTOID) { if (type == CSTRINGOID) + { string = DatumGetCString(arg); + if (pg_strcasecmp(string, "true") == 0) + result = true; + else if (pg_strcasecmp(string, "false") == 0) + result = false; + else + PG_RETURN_NULL(); + } else - string = text_to_cstring(DatumGetTextPP(arg)); - - if (pg_strcasecmp(string, "true") == 0) - result = true; - else if (pg_strcasecmp(string, "false") == 0) - result = false; - else - PG_RETURN_NULL(); + { + text *txt = DatumGetTextPP(arg); + int len = VARSIZE_ANY_EXHDR(txt); + + string = VARDATA_ANY(txt); + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) + result = true; + else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) + result = false; + else + PG_RETURN_NULL(); + } } else if (type == INT2OID || type == INT4OID || type == INT8OID) { @@ -7350,8 +7331,7 @@ Datum age_size(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); - result = strlen(string); + result = VARSIZE_ANY_EXHDR(DatumGetTextPP(arg)); } else if (type == AGTYPEOID) { @@ -7508,8 +7488,7 @@ Datum age_isempty(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); - result = strlen(string); + result = VARSIZE_ANY_EXHDR(DatumGetTextPP(arg)); } else if (type == AGTYPEOID) { @@ -7685,6 +7664,7 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { agtype_value *agtv_result = NULL; char *string = NULL; + int string_len = -1; /* * toString() supports: unknown, integer, float, numeric, text, cstring, @@ -7700,7 +7680,8 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { char *str = DatumGetPointer(arg); - string = pnstrdup(str, strlen(str)); + string = str; + string_len = strlen(str); } /* if it is not an AGTYPEOID */ else if (type != AGTYPEOID) @@ -7737,7 +7718,10 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else if (type == TEXTOID) { - string = text_to_cstring(DatumGetTextPP(arg)); + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); } else if (type == BOOLOID) { @@ -7788,8 +7772,8 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else if (agtv_value->type == AGTV_STRING) { - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = agtv_value->val.string.val; + string_len = agtv_value->val.string.len; } else if (agtv_value->type == AGTV_NUMERIC) { @@ -7819,7 +7803,7 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) agtv_result = palloc(sizeof(agtype_value)); agtv_result->type = AGTV_STRING; agtv_result->val.string.val = string; - agtv_result->val.string.len = strlen(string); + agtv_result->val.string.len = string_len >= 0 ? string_len : strlen(string); return agtv_result; } @@ -7978,10 +7962,7 @@ PG_FUNCTION_INFO_V1(age_reverse); Datum age_reverse(PG_FUNCTION_ARGS) { Datum arg; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; if (!get_single_variadic_arg(fcinfo, "reverse()", true, &arg, &type)) @@ -8110,16 +8091,7 @@ Datum age_reverse(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(text_reverse, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_toupper); @@ -8143,14 +8115,21 @@ Datum age_toupper(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == CSTRINGOID) + { string = DatumGetCString(arg); + string_len = strlen(string); + } else if (type == TEXTOID) - string = text_to_cstring(DatumGetTextPP(arg)); + { + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); + } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() unsupported argument type %d", type))); - string_len = strlen(string); } else { @@ -8216,14 +8195,21 @@ Datum age_tolower(PG_FUNCTION_ARGS) if (type != AGTYPEOID) { if (type == CSTRINGOID) + { string = DatumGetCString(arg); + string_len = strlen(string); + } else if (type == TEXTOID) - string = text_to_cstring(DatumGetTextPP(arg)); + { + text *txt = DatumGetTextPP(arg); + + string = VARDATA_ANY(txt); + string_len = VARSIZE_ANY_EXHDR(txt); + } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() unsupported argument type %d", type))); - string_len = strlen(string); } else { @@ -8273,10 +8259,7 @@ PG_FUNCTION_INFO_V1(age_rtrim); Datum age_rtrim(PG_FUNCTION_ARGS) { Datum arg; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; if (!get_single_variadic_arg(fcinfo, "rTrim()", true, &arg, &type)) @@ -8329,16 +8312,7 @@ Datum age_rtrim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(rtrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_ltrim); @@ -8346,10 +8320,7 @@ PG_FUNCTION_INFO_V1(age_ltrim); Datum age_ltrim(PG_FUNCTION_ARGS) { Datum arg; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; if (!get_single_variadic_arg(fcinfo, "lTrim()", true, &arg, &type)) @@ -8402,16 +8373,7 @@ Datum age_ltrim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(ltrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_trim); @@ -8419,10 +8381,7 @@ PG_FUNCTION_INFO_V1(age_trim); Datum age_trim(PG_FUNCTION_ARGS) { Datum arg; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; - int string_len; Oid type; if (!get_single_variadic_arg(fcinfo, "trim()", true, &arg, &type)) @@ -8475,16 +8434,7 @@ Datum age_trim(PG_FUNCTION_ARGS) text_string = DatumGetTextPP(DirectFunctionCall1(btrim1, PointerGetDatum(text_string))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_right); @@ -8499,9 +8449,7 @@ Datum age_right(PG_FUNCTION_ARGS) bool fast_nulls[2]; Oid *types; Oid fast_types[2]; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; int64 string_len; Oid type; @@ -8649,16 +8597,7 @@ Datum age_right(PG_FUNCTION_ARGS) PointerGetDatum(text_string), Int64GetDatum(string_len))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_left); @@ -8673,9 +8612,7 @@ Datum age_left(PG_FUNCTION_ARGS) bool fast_nulls[2]; Oid *types; Oid fast_types[2]; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; int64 string_len; Oid type; @@ -8830,16 +8767,7 @@ Datum age_left(PG_FUNCTION_ARGS) PointerGetDatum(text_string), Int64GetDatum(string_len))); - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_substring); @@ -8854,9 +8782,7 @@ Datum age_substring(PG_FUNCTION_ARGS) bool fast_nulls[3]; Oid *types; Oid fast_types[3]; - agtype_value agtv_result; text *text_string = NULL; - char *string = NULL; int64 param; int string_start = 0; int string_len = 0; @@ -9051,16 +8977,7 @@ Datum age_substring(PG_FUNCTION_ARGS) Int64GetDatum(string_start), Int64GetDatum(string_len))); } - /* convert it back to a cstring */ - string = text_to_cstring(text_string); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_string)); } PG_FUNCTION_INFO_V1(age_split); @@ -9226,14 +9143,11 @@ Datum age_replace(PG_FUNCTION_ARGS) bool fast_nulls[3]; Oid *types; Oid fast_types[3]; - agtype_value agtv_result; text *param = NULL; text *text_string = NULL; text *text_search = NULL; text *text_replace = NULL; text *text_result = NULL; - char *string = NULL; - int string_len; Oid type; int i; @@ -9311,16 +9225,7 @@ Datum age_replace(PG_FUNCTION_ARGS) replace_text, C_COLLATION_OID, PointerGetDatum(text_string), PointerGetDatum(text_search), PointerGetDatum(text_replace))); - /* convert it back to a cstring */ - string = text_to_cstring(text_result); - string_len = strlen(string); - - /* build the result */ - agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = string; - agtv_result.val.string.len = string_len; - - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + PG_RETURN_POINTER(text_to_agtype_string_value(text_result)); } /* @@ -11905,8 +11810,11 @@ agtype_value *get_agtype_value(char *funcname, agtype *agt_arg, funcname))); } + /* get the agtype value */ + agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + /* is it AGTV_NULL? */ - if (error && is_agtype_null(agt_arg)) + if (error && agtv_value->type == AGTV_NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -11914,9 +11822,6 @@ agtype_value *get_agtype_value(char *funcname, agtype *agt_arg, funcname))); } - /* get the agtype value */ - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* is it the correct type? */ if (error && agtv_value->type != type) { @@ -12877,9 +12782,11 @@ Datum agtype_volatile_wrapper(PG_FUNCTION_ARGS) } else if (type == TEXTOID) { + text *txt = DatumGetTextPP(arg); + agtv_result.type = AGTV_STRING; - agtv_result.val.string.val = text_to_cstring(DatumGetTextPP(arg)); - agtv_result.val.string.len = strlen(agtv_result.val.string.val); + agtv_result.val.string.val = VARDATA_ANY(txt); + agtv_result.val.string.len = VARSIZE_ANY_EXHDR(txt); } else { diff --git a/src/backend/utils/ag_func.c b/src/backend/utils/ag_func.c index 2f1b5ce99..2eed1b1ab 100644 --- a/src/backend/utils/ag_func.c +++ b/src/backend/utils/ag_func.c @@ -118,6 +118,8 @@ Oid get_ag_func_oid(const char *func_name, const int nargs, ...) CStringGetDatum(func_name), PointerGetDatum(arg_types), ObjectIdGetDatum(ag_catalog_namespace_id())); + pfree(arg_types); + if (!OidIsValid(func_oid)) { ereport(ERROR, (errmsg_internal("ag function does not exist"), @@ -224,6 +226,8 @@ Oid get_pg_func_oid(const char *func_name, const int nargs, ...) CStringGetDatum(func_name), PointerGetDatum(arg_types), ObjectIdGetDatum(pg_catalog_namespace_id())); + pfree(arg_types); + if (!OidIsValid(func_oid)) { ereport(ERROR, (errmsg_internal("pg function does not exist"), diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index e405b083b..9c72a6ecb 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -1164,7 +1164,7 @@ static void *label_name_graph_cache_hash_search(Name name, Oid graph, label_name_graph_cache_key key; /* initialize the hash key for label_name_graph_cache_hash */ - namestrcpy(&key.name, name->data); + key.name = *name; key.graph = graph; return hash_search(label_name_graph_cache_hash, &key, action, found); @@ -1495,7 +1495,7 @@ static void *label_seq_name_graph_cache_hash_search(Name name, Oid graph, label_seq_name_graph_cache_key key; /* initialize the hash key for label_seq_name_graph_cache_hash */ - namestrcpy(&key.name, name->data); + key.name = *name; key.graph = graph; return hash_search(label_seq_name_graph_cache_hash, &key, action, found); diff --git a/src/backend/utils/load/ag_load_edges.c b/src/backend/utils/load/ag_load_edges.c index 5e1ee7b4d..f51de4995 100644 --- a/src/backend/utils/load/ag_load_edges.c +++ b/src/backend/utils/load/ag_load_edges.c @@ -37,9 +37,20 @@ typedef struct vertex_label_id_cache_entry int32 id; } vertex_label_id_cache_entry; -static HTAB *create_vertex_label_id_cache(void); -static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, +typedef struct vertex_label_id_cache +{ + HTAB *entries; + NameData last_name; + int32 last_id; + bool last_valid; +} vertex_label_id_cache; + +static vertex_label_id_cache *create_vertex_label_id_cache(void); +static void destroy_vertex_label_id_cache(vertex_label_id_cache *cache); +static int32 get_cached_vertex_label_id(vertex_label_id_cache *cache, + const char *label_name, Oid graph_oid); +static void trim_field_to_name(Name name, const char *str); /* * Process a single edge row from COPY's raw fields. @@ -50,7 +61,7 @@ static void process_edge_row(char **fields, int nfields, int label_id, Oid label_seq_relid, Oid graph_oid, bool load_as_agtype, batch_insert_state *batch_state, - HTAB *vertex_label_id_cache) + vertex_label_id_cache *vertex_label_id_cache) { int64 start_id_int; graphid start_vertex_graph_id; @@ -64,8 +75,8 @@ static void process_edge_row(char **fields, int nfields, int64 entry_id; TupleTableSlot *slot; - char *start_vertex_type; - char *end_vertex_type; + NameData start_vertex_type; + NameData end_vertex_type; agtype *edge_properties; /* Generate edge ID */ @@ -73,19 +84,19 @@ static void process_edge_row(char **fields, int nfields, edge_id = make_graphid(label_id, entry_id); /* Trim whitespace from vertex type names */ - start_vertex_type = trim_whitespace(fields[1]); - end_vertex_type = trim_whitespace(fields[3]); + trim_field_to_name(&start_vertex_type, fields[1]); + trim_field_to_name(&end_vertex_type, fields[3]); /* Parse start vertex info */ start_id_int = strtol(fields[0], NULL, 10); start_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, - start_vertex_type, + NameStr(start_vertex_type), graph_oid); /* Parse end vertex info */ end_id_int = strtol(fields[2], NULL, 10); end_vertex_type_id = get_cached_vertex_label_id(vertex_label_id_cache, - end_vertex_type, + NameStr(end_vertex_type), graph_oid); /* Create graphids for start and end vertices */ @@ -128,27 +139,97 @@ static void process_edge_row(char **fields, int nfields, } } -static HTAB *create_vertex_label_id_cache(void) +static void trim_field_to_name(Name name, const char *str) +{ + const char *start; + const char *p; + const char *last_non_space = NULL; + size_t len; + + if (str == NULL) + { + NameStr(*name)[0] = '\0'; + return; + } + + start = str; + while (*start == ' ' || *start == '\t' || + *start == '\n' || *start == '\r') + { + start++; + } + + if (*start == '\0') + { + NameStr(*name)[0] = '\0'; + return; + } + + for (p = start; *p != '\0'; p++) + { + if (*p != ' ' && *p != '\t' && + *p != '\n' && *p != '\r') + { + last_non_space = p; + } + } + + if (last_non_space == NULL) + { + NameStr(*name)[0] = '\0'; + return; + } + + len = last_non_space - start + 1; + if (len >= NAMEDATALEN) + { + len = NAMEDATALEN - 1; + } + + memcpy(NameStr(*name), start, len); + NameStr(*name)[len] = '\0'; +} + +static vertex_label_id_cache *create_vertex_label_id_cache(void) { HASHCTL hashctl; + vertex_label_id_cache *cache; MemSet(&hashctl, 0, sizeof(hashctl)); hashctl.keysize = sizeof(NameData); hashctl.entrysize = sizeof(vertex_label_id_cache_entry); - return hash_create("age edge load vertex label id cache", 16, &hashctl, - HASH_ELEM | HASH_BLOBS); + cache = palloc(sizeof(vertex_label_id_cache)); + cache->entries = hash_create("age edge load vertex label id cache", 16, + &hashctl, HASH_ELEM | HASH_BLOBS); + cache->last_valid = false; + cache->last_id = -1; + + return cache; +} + +static void destroy_vertex_label_id_cache(vertex_label_id_cache *cache) +{ + hash_destroy(cache->entries); + pfree(cache); } -static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, +static int32 get_cached_vertex_label_id(vertex_label_id_cache *cache, + const char *label_name, Oid graph_oid) { NameData key; vertex_label_id_cache_entry *entry; bool found; + if (cache->last_valid && + namestrcmp(&cache->last_name, label_name) == 0) + { + return cache->last_id; + } + namestrcpy(&key, label_name); - entry = hash_search(cache, &key, HASH_ENTER, &found); + entry = hash_search(cache->entries, &key, HASH_ENTER, &found); if (!found) { label_cache_data *label_cache; @@ -158,6 +239,10 @@ static int32 get_cached_vertex_label_id(HTAB *cache, const char *label_name, entry->id = label_cache != NULL ? label_cache->id : -1; } + cache->last_name = key; + cache->last_id = entry->id; + cache->last_valid = true; + return entry->id; } @@ -208,7 +293,7 @@ int create_edges_from_csv_file(char *file_path, batch_insert_state *batch_state = NULL; MemoryContext batch_context; MemoryContext old_context; - HTAB *vertex_label_id_cache = NULL; + vertex_label_id_cache *vertex_label_id_cache = NULL; /* Create a memory context for batch processing - reset after each batch */ batch_context = AllocSetContextCreate(CurrentMemoryContext, @@ -291,7 +376,7 @@ int create_edges_from_csv_file(char *file_path, /* Finish any remaining batch inserts */ finish_batch_insert(&batch_state); MemoryContextReset(batch_context); - hash_destroy(vertex_label_id_cache); + destroy_vertex_label_id_cache(vertex_label_id_cache); vertex_label_id_cache = NULL; /* Clean up COPY state */ @@ -315,7 +400,7 @@ int create_edges_from_csv_file(char *file_path, if (vertex_label_id_cache != NULL) { - hash_destroy(vertex_label_id_cache); + destroy_vertex_label_id_cache(vertex_label_id_cache); } /* Delete batch context */ diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index d0532644b..dc028ef15 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -40,6 +40,8 @@ #include "utils/ag_cache.h" static agtype_value *csv_value_to_agtype_value(char *csv_val); +static agtype_value *trimmed_field_to_string_agtype_value(const char *field); +static const char *trimmed_string_span(const char *str, size_t *len); static Oid get_or_create_graph(const Name graph_name); static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, char *label_name, @@ -51,6 +53,8 @@ static void check_rls_for_load(Oid relid); #define AGE_BASE_CSV_DIRECTORY "/tmp/age/" #define AGE_CSV_FILE_EXTENSION ".csv" +#define AGE_BASE_CSV_DIRECTORY_LEN (sizeof(AGE_BASE_CSV_DIRECTORY) - 1) +#define AGE_CSV_FILE_EXTENSION_LEN (sizeof(AGE_CSV_FILE_EXTENSION) - 1) /* * Trim leading and trailing whitespace from a string. @@ -60,39 +64,48 @@ static void check_rls_for_load(Oid relid); char *trim_whitespace(const char *str) { const char *start; - const char *end; size_t len; + start = trimmed_string_span(str, &len); + return pnstrdup(start, len); +} + +static const char *trimmed_string_span(const char *str, size_t *len) +{ + const char *start; + const char *p; + const char *last_non_space = NULL; + if (str == NULL) { - return pstrdup(""); + *len = 0; + return ""; } - /* Find first non-whitespace character */ start = str; - while (*start && (*start == ' ' || *start == '\t' || - *start == '\n' || *start == '\r')) + while (*start == ' ' || *start == '\t' || + *start == '\n' || *start == '\r') { start++; } - /* If string is all whitespace, return empty string */ - if (*start == '\0') + for (p = start; *p != '\0'; p++) { - return pstrdup(""); + if (*p != ' ' && *p != '\t' && + *p != '\n' && *p != '\r') + { + last_non_space = p; + } } - /* Find last non-whitespace character */ - end = str + strlen(str) - 1; - while (end > start && (*end == ' ' || *end == '\t' || - *end == '\n' || *end == '\r')) + if (last_non_space == NULL) { - end--; + *len = 0; + return ""; } - /* Copy the trimmed string */ - len = end - start + 1; - return pnstrdup(start, len); + *len = last_non_space - start + 1; + return start; } static char *build_safe_filename(char *name) @@ -128,16 +141,16 @@ static char *build_safe_filename(char *name) } if (strncmp(resolved, AGE_BASE_CSV_DIRECTORY, - strlen(AGE_BASE_CSV_DIRECTORY)) != 0) + AGE_BASE_CSV_DIRECTORY_LEN) != 0) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("You can only load files located in [%s].", AGE_BASE_CSV_DIRECTORY))); } - length = strlen(resolved) - 4; + length = strlen(resolved) - AGE_CSV_FILE_EXTENSION_LEN; if (strncmp(resolved+length, AGE_CSV_FILE_EXTENSION, - strlen(AGE_CSV_FILE_EXTENSION)) != 0) + AGE_CSV_FILE_EXTENSION_LEN) != 0) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("You can only load files with extension [%s].", @@ -261,6 +274,23 @@ static agtype_value *csv_value_to_agtype_value(char *csv_val) return res; } +static agtype_value *trimmed_field_to_string_agtype_value(const char *field) +{ + const char *start; + size_t len; + agtype_value *agtv; + + agtv = palloc(sizeof(agtype_value)); + agtv->type = AGTV_STRING; + + start = trimmed_string_span(field, &len); + len = check_string_length(len); + agtv->val.string.len = len; + agtv->val.string.val = pnstrdup(start, len); + + return agtv; +} + agtype *create_agtype_from_list(char **header, char **fields, size_t fields_len, int64 vertex_id, bool load_as_agtype) { @@ -287,8 +317,6 @@ agtype *create_agtype_from_list(char **header, char **fields, size_t fields_len, for (i = 0; itype = AGTV_STRING; - value_agtype->val.string.len = 0; - value_agtype->val.string.val = pstrdup(""); - } - else - { - value_agtype = string_to_agtype_value(trimmed_value); - } + value_agtype = trimmed_field_to_string_agtype_value(fields[i]); } result.res = push_agtype_value(&result.parse_state, @@ -362,8 +380,6 @@ agtype* create_agtype_from_list_i(char **header, char **fields, for (i = start_index; i < fields_len; i++) { - char *trimmed_value; - /* Skip empty header fields (e.g., from trailing commas) */ if (header[i] == NULL || header[i][0] == '\0') { @@ -375,27 +391,17 @@ agtype* create_agtype_from_list_i(char **header, char **fields, WAGT_KEY, key_agtype); - /* Trim whitespace from field value */ - trimmed_value = trim_whitespace(fields[i]); - if (load_as_agtype) { + char *trimmed_value; + + /* Trim whitespace from field value */ + trimmed_value = trim_whitespace(fields[i]); value_agtype = csv_value_to_agtype_value(trimmed_value); } else { - /* Handle empty field values */ - if (trimmed_value[0] == '\0') - { - value_agtype = palloc(sizeof(agtype_value)); - value_agtype->type = AGTV_STRING; - value_agtype->val.string.len = 0; - value_agtype->val.string.val = pstrdup(""); - } - else - { - value_agtype = string_to_agtype_value(trimmed_value); - } + value_agtype = trimmed_field_to_string_agtype_value(fields[i]); } result.res = push_agtype_value(&result.parse_state, @@ -617,7 +623,7 @@ Datum load_labels_from_file(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); label_name_str = NameStr(*label_name); - if (strcmp(label_name_str, "") == 0) + if (label_name_str[0] == '\0') { label_name_str = AG_DEFAULT_LABEL_VERTEX; } @@ -689,7 +695,7 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); label_name_str = NameStr(*label_name); - if (strcmp(label_name_str, "") == 0) + if (label_name_str[0] == '\0') { label_name_str = AG_DEFAULT_LABEL_EDGE; } @@ -843,7 +849,7 @@ void init_batch_insert(batch_insert_state **batch_state, Oid relid) ExecOpenIndices(resultRelInfo, false); /* Initialize the batch insert state */ - *batch_state = (batch_insert_state *) palloc0(sizeof(batch_insert_state)); + *batch_state = (batch_insert_state *) palloc(sizeof(batch_insert_state)); (*batch_state)->slots = palloc(sizeof(TupleTableSlot *) * BATCH_SIZE); (*batch_state)->estate = estate; (*batch_state)->resultRelInfo = resultRelInfo; diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index f90e682eb..edf08c860 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -66,6 +66,7 @@ typedef struct cypher_set_custom_scan_state CustomScanState css; CustomScan *cs; cypher_update_information *set_list; + int set_item_count; HTAB *qual_cache; HTAB *index_cache; HTAB *result_rel_info_cache; @@ -123,6 +124,8 @@ typedef struct cypher_merge_custom_scan_state bool eager_buffer_filled; cypher_update_information *on_match_set_info; /* NULL if not specified */ cypher_update_information *on_create_set_info; /* NULL if not specified */ + int on_match_set_item_count; + int on_create_set_item_count; HTAB *update_qual_cache; HTAB *update_index_cache; HTAB *update_result_rel_info_cache; @@ -132,7 +135,8 @@ typedef struct cypher_merge_custom_scan_state /* Reusable SET logic callable from MERGE executor */ void apply_update_list(CustomScanState *node, - cypher_update_information *set_info); + cypher_update_information *set_info, + int num_set_items); TupleTableSlot *populate_vertex_tts(TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties); From af14131de0799512c50c3affc3791497a6fbb879 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:38 +0900 Subject: [PATCH 09/15] Use direct scalar string conversions Replace several agtype scalar conversion round trips with direct length-aware integer, float, numeric, boolean, and text formatting paths. This avoids reparsing list values, repeated output length scans, stale label and proc-name comparisons, and unnecessary string serialization in toString, toStringList, typecast, concat, and agtype output helpers. The result keeps the same values while reducing allocation and formatting overhead in tight scalar loops. --- src/backend/catalog/ag_label.c | 10 +- src/backend/commands/label_commands.c | 6 +- src/backend/executor/cypher_set.c | 14 +- src/backend/parser/cypher_expr.c | 69 +++-- src/backend/parser/cypher_gram.y | 12 +- src/backend/utils/adt/age_vle.c | 25 +- src/backend/utils/adt/agtype.c | 399 +++++++++++++------------- src/backend/utils/adt/agtype_gin.c | 8 +- src/backend/utils/adt/agtype_ops.c | 14 +- src/backend/utils/adt/graphid.c | 5 +- src/backend/utils/cache/ag_cache.c | 10 +- src/backend/utils/load/age_load.c | 46 +-- src/include/utils/ag_cache.h | 1 + src/include/utils/agtype.h | 1 + 14 files changed, 345 insertions(+), 275 deletions(-) diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index c6b20c39d..b0c6d834d 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -146,7 +146,13 @@ Oid get_label_relation(const char *label_name, Oid graph_oid) char *get_label_relation_name(const char *label_name, Oid graph_oid) { - return get_rel_name(get_label_relation(label_name, graph_oid)); + label_cache_data *cache_data; + + cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); + if (cache_data) + return pstrdup(NameStr(cache_data->relation_name)); + else + return NULL; } char get_label_kind(const char *label_name, Oid label_graph) @@ -269,7 +275,7 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); - relname = get_rel_name(label_cache->relation); + relname = pstrdup(NameStr(label_cache->relation_name)); return makeRangeVar(graph_name, relname, 2); } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index fbf0ed1f5..fe937d242 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -642,14 +642,16 @@ static void create_sequence_for_label(RangeVar *seq_range_var) /* greater than MAXINT8LEN+1 */ char buf[32]; DefElem *maxvalue; + int len; pstate = make_parsestate(NULL); pstate->p_sourcetext = "(generated CREATE SEQUENCE command)"; seq_stmt = makeNode(CreateSeqStmt); seq_stmt->sequence = seq_range_var; - pg_lltoa(ENTRY_ID_MAX, buf); - maxvalue = makeDefElem("maxvalue", (Node *)makeFloat(pstrdup(buf)), -1); + len = pg_lltoa(ENTRY_ID_MAX, buf); + maxvalue = makeDefElem("maxvalue", (Node *)makeFloat(pnstrdup(buf, len)), + -1); seq_stmt->options = list_make1(maxvalue); seq_stmt->ownerId = InvalidOid; seq_stmt->for_identity = false; diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 4ccd4222d..b394843c2 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -420,26 +420,30 @@ static void update_all_paths(CustomScanState *node, graphid id, { ExprContext *econtext = node->ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; + TupleDesc tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; + Datum *values = scanTupleSlot->tts_values; + bool *isnull = scanTupleSlot->tts_isnull; + int natts = tupleDescriptor->natts; int i; - for (i = 0; i < scanTupleSlot->tts_tupleDescriptor->natts; i++) + for (i = 0; i < natts; i++) { agtype *original_entity; agtype_value *original_entity_value; /* skip nulls */ - if (TupleDescAttr(scanTupleSlot->tts_tupleDescriptor, i)->atttypid != AGTYPEOID) + if (TupleDescAttr(tupleDescriptor, i)->atttypid != AGTYPEOID) { continue; } /* skip non agtype values */ - if (scanTupleSlot->tts_isnull[i]) + if (isnull[i]) { continue; } - original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[i]); + original_entity = DATUM_GET_AGTYPE_P(values[i]); /* if the value is not a scalar type, its not a path */ if (!AGTYPE_CONTAINER_IS_SCALAR(&original_entity->root)) @@ -458,7 +462,7 @@ static void update_all_paths(CustomScanState *node, graphid id, /* the path does contain the entity replace with the new entity. */ agtype_value *new_path = replace_entity_in_path(original_entity_value, id, updated_entity); - scanTupleSlot->tts_values[i] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); + values[i] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); } } } diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index c585eaed6..934859ba8 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -153,8 +153,8 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found); static const char *get_mapped_extension(Oid func_oid); static bool function_belongs_to_extension(Oid func_oid, const char *extension); static bool is_extension_external(const char *extension); -static bool function_needs_graph_name_argument(const char *name); -static char *construct_age_function_name(char *funcname); +static bool function_needs_graph_name_argument(const char *name, int name_len); +static char *construct_age_function_name(char *funcname, int funcname_len); static void initialize_function_caches(void); static void invalidate_function_caches(Datum arg, int cache_id, uint32 hash_value); @@ -1623,47 +1623,55 @@ static Node *transform_cypher_typecast(cypher_parsestate *cpstate, if (list_length(target_typ->names) == 1) { char *typecast = strVal(linitial(target_typ->names)); + int typecast_len = strlen(typecast); /* append the name of the requested typecast function */ - if (pg_strcasecmp(typecast, "edge") == 0) + if (typecast_len == 4 && pg_strcasecmp(typecast, "edge") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); } - else if (pg_strcasecmp(typecast, "path") == 0) + else if (typecast_len == 4 && pg_strcasecmp(typecast, "path") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); } - else if (pg_strcasecmp(typecast, "vertex") == 0) + else if (typecast_len == 6 && pg_strcasecmp(typecast, "vertex") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); } - else if (pg_strcasecmp(typecast, "numeric") == 0) + else if (typecast_len == 7 && pg_strcasecmp(typecast, "numeric") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); } - else if (pg_strcasecmp(typecast, "float") == 0) + else if (typecast_len == 5 && pg_strcasecmp(typecast, "float") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); } - else if (pg_strcasecmp(typecast, "int") == 0 || - pg_strcasecmp(typecast, "integer") == 0) + else if ((typecast_len == 3 && + pg_strcasecmp(typecast, "int") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "integer") == 0)) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); } - else if (pg_strcasecmp(typecast, "pg_float8") == 0) + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_float8") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); } - else if (pg_strcasecmp(typecast, "pg_bigint") == 0) + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_bigint") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); } - else if ((pg_strcasecmp(typecast, "bool") == 0 || - pg_strcasecmp(typecast, "boolean") == 0)) + else if ((typecast_len == 4 && + pg_strcasecmp(typecast, "bool") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "boolean") == 0)) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); } - else if (pg_strcasecmp(typecast, "pg_text") == 0) + else if (typecast_len == 7 && + pg_strcasecmp(typecast, "pg_text") == 0) { fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_TEXT)); } @@ -1883,6 +1891,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) bool asp_fetched = false; bool found = false; char *funcname = (((String*)linitial(fn->funcname))->sval); + int funcname_len = strlen(funcname); /* get a list of matching functions */ catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); @@ -1906,9 +1915,10 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) * variadic match before checking if it is in the search * path. */ - if (pg_strcasecmp(funcname, procform->proname.data) == 0 && - nargs == procform->pronargs && - fn->func_variadic == procform->provariadic) + if (nargs == procform->pronargs && + fn->func_variadic == procform->provariadic && + strnlen(NameStr(procform->proname), NAMEDATALEN) == funcname_len && + pg_strcasecmp(funcname, procform->proname.data) == 0) { if (!asp_fetched) { @@ -1947,7 +1957,7 @@ static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) /* we need to release the cache list */ ReleaseSysCacheList(catlist); - pfree_if_not_null(asp); + list_free(asp); return result; } @@ -2051,27 +2061,27 @@ static bool is_extension_external(const char *extension) (pg_strcasecmp(extension, "age") != 0)); } -static bool function_needs_graph_name_argument(const char *name) +static bool function_needs_graph_name_argument(const char *name, int name_len) { switch (name[0]) { case 'e': - return strcmp(name, "endNode") == 0; + return name_len == 7 && memcmp(name, "endNode", 7) == 0; case 's': - return strcmp(name, "startNode") == 0; + return name_len == 9 && memcmp(name, "startNode", 9) == 0; case 'v': - return (strcmp(name, "vle") == 0 || - strcmp(name, "vertex_stats") == 0); + return (name_len == 3 && memcmp(name, "vle", 3) == 0) || + (name_len == 12 && + memcmp(name, "vertex_stats", 12) == 0); default: return false; } } /* Returns age_ prefiexed lower case function name */ -static char *construct_age_function_name(char *funcname) +static char *construct_age_function_name(char *funcname, int funcname_len) { - int pnlen = strlen(funcname); - char *ag_name = palloc(pnlen + 5); + char *ag_name = palloc(funcname_len + 5); int i; /* copy in the prefix - all AGE functions are prefixed with age_ */ @@ -2081,7 +2091,7 @@ static char *construct_age_function_name(char *funcname) * All AGE function names are in lower case. So, copy in the funcname * in lower case. */ - for (i = 0; i < pnlen; i++) + for (i = 0; i < funcname_len; i++) { ag_name[i + 4] = tolower(funcname[i]); } @@ -2472,7 +2482,8 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) else { char *name = strVal(linitial(fn->funcname)); - char *ag_name = construct_age_function_name(name); + int name_len = strlen(name); + char *ag_name = construct_age_function_name(name, name_len); if (function_exists(ag_name, "age")) { @@ -2486,7 +2497,7 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) * is not empty. Then prepend the graph name if necessary. */ if ((targs != NIL) && - function_needs_graph_name_argument(name)) + function_needs_graph_name_argument(name, name_len)) { char *graph_name = cpstate->graph_name; Datum d = string_to_agtype(graph_name); diff --git a/src/backend/parser/cypher_gram.y b/src/backend/parser/cypher_gram.y index 52a57b70d..e76251fcb 100644 --- a/src/backend/parser/cypher_gram.y +++ b/src/backend/parser/cypher_gram.y @@ -257,6 +257,7 @@ static Node *make_typecast_expr(Node *expr, Node *typname, int location); static Node *make_function_expr(List *func_name, List *exprs, int location); static Node *make_star_function_expr(List *func_name, List *exprs, int location); static Node *make_distinct_function_expr(List *func_name, List *exprs, int location); +static bool is_count_function_name(const char *name); static FuncCall *node_to_agtype(Node* fnode, char *type, int location); /* setops */ @@ -2823,7 +2824,7 @@ static Node *make_function_expr(List *func_name, List *exprs, int location) * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2878,7 +2879,7 @@ static Node *make_star_function_expr(List *func_name, List *exprs, int location) * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2935,7 +2936,7 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat * functions. We may want to find a better way to do this, as there * could be many. */ - if (pg_strcasecmp(name, "count") == 0) + if (is_count_function_name(name)) { funcname = SystemFuncName("count"); @@ -2972,6 +2973,11 @@ static Node *make_distinct_function_expr(List *func_name, List *exprs, int locat return (Node *)fnode; } +static bool is_count_function_name(const char *name) +{ + return strlen(name) == 5 && pg_strcasecmp(name, "count") == 0; +} + /* * helper function to wrap pg_function in the appropiate typecast function to * interface with AGE components diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 70dfef839..af54a8153 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -861,14 +861,33 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, agtv_temp->val.string.len != 0) { label_cache_data *label_cache; + char label_name_buf[NAMEDATALEN]; + char *label_name; + bool free_label_name = false; - vlelctx->edge_label_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + if (agtv_temp->val.string.len < NAMEDATALEN) + { + memcpy(label_name_buf, agtv_temp->val.string.val, + agtv_temp->val.string.len); + label_name_buf[agtv_temp->val.string.len] = '\0'; + label_name = label_name_buf; + } + else + { + label_name = pnstrdup(agtv_temp->val.string.val, + agtv_temp->val.string.len); + free_label_name = true; + } label_cache = search_label_name_graph_cache_cached( - vlelctx->edge_label_name, graph_oid); + label_name, graph_oid); vlelctx->edge_label_name_oid = label_cache != NULL ? label_cache->relation : InvalidOid; + vlelctx->edge_label_name = NULL; + if (free_label_name) + { + pfree(label_name); + } } else { diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 463c7446c..c151608c7 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -1103,6 +1103,7 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, bool extend) { char *numstr; + int numstr_len; switch (scalar_val->type) { @@ -1114,25 +1115,30 @@ static void agtype_put_escaped_value(StringInfo out, agtype_value *scalar_val, scalar_val->val.string.len); break; case AGTV_NUMERIC: - appendStringInfoString( - out, DatumGetCString(DirectFunctionCall1( - numeric_out, PointerGetDatum(scalar_val->val.numeric)))); + numstr = DatumGetCString(DirectFunctionCall1( + numeric_out, PointerGetDatum(scalar_val->val.numeric))); + numstr_len = strlen(numstr); + appendBinaryStringInfo(out, numstr, numstr_len); if (extend) { appendBinaryStringInfo(out, "::numeric", 9); } break; case AGTV_INTEGER: - appendStringInfoString( - out, DatumGetCString(DirectFunctionCall1( - int8out, Int64GetDatum(scalar_val->val.int_value)))); + { + char intbuf[24]; + int intbuf_len; + + intbuf_len = pg_lltoa(scalar_val->val.int_value, intbuf); + appendBinaryStringInfo(out, intbuf, intbuf_len); break; + } case AGTV_FLOAT: - numstr = DatumGetCString(DirectFunctionCall1( - float8out, Float8GetDatum(scalar_val->val.float_value))); - appendStringInfoString(out, numstr); + numstr = float8out_internal(scalar_val->val.float_value); + numstr_len = strlen(numstr); + appendBinaryStringInfo(out, numstr, numstr_len); - if (is_decimal_needed(numstr)) + if (is_decimal_needed_len(numstr, numstr_len)) appendBinaryStringInfo(out, ".0", 2); break; case AGTV_BOOL: @@ -1232,6 +1238,13 @@ static void escape_agtype(StringInfo buf, const char *str, int len) } bool is_decimal_needed(char *numstr) +{ + Assert(numstr); + + return is_decimal_needed_len(numstr, strlen(numstr)); +} + +bool is_decimal_needed_len(const char *numstr, int len) { int i; @@ -1239,7 +1252,7 @@ bool is_decimal_needed(char *numstr) i = (numstr[0] == '-') ? 1 : 0; - while (numstr[i] != '\0') + while (i < len) { if (numstr[i] < '0' || numstr[i] > '9') return false; @@ -1533,23 +1546,37 @@ static text *agtype_value_to_text(agtype_value *scalar_val, switch (scalar_val->type) { case AGTV_INTEGER: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - int8out, Int64GetDatum(scalar_val->val.int_value)))); + { + char intbuf[24]; + int intbuf_len; + + intbuf_len = pg_lltoa(scalar_val->val.int_value, intbuf); + result = cstring_to_text_with_len(intbuf, intbuf_len); break; + } case AGTV_FLOAT: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - float8out, Float8GetDatum(scalar_val->val.float_value)))); + { + char *numstr = float8out_internal(scalar_val->val.float_value); + + result = cstring_to_text_with_len(numstr, strlen(numstr)); break; + } case AGTV_STRING: result = cstring_to_text_with_len(scalar_val->val.string.val, scalar_val->val.string.len); break; case AGTV_NUMERIC: - result = cstring_to_text(DatumGetCString(DirectFunctionCall1( - numeric_out, PointerGetDatum(scalar_val->val.numeric)))); + { + char *numstr = DatumGetCString(DirectFunctionCall1( + numeric_out, PointerGetDatum(scalar_val->val.numeric))); + + result = cstring_to_text_with_len(numstr, strlen(numstr)); break; + } case AGTV_BOOL: - result = cstring_to_text((scalar_val->val.boolean) ? "true" : "false"); + result = scalar_val->val.boolean ? + cstring_to_text_with_len("true", 4) : + cstring_to_text_with_len("false", 5); break; case AGTV_NULL: result = NULL; @@ -1772,6 +1799,7 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, bool key_scalar) { char *outputstr; + int outputstr_len; bool numeric_error; agtype_value agtv; bool scalar_agtype = false; @@ -1811,9 +1839,11 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, case AGT_TYPE_BOOL: if (key_scalar) { - outputstr = DatumGetBool(val) ? "true" : "false"; + bool boolval = DatumGetBool(val); + + outputstr = boolval ? "true" : "false"; agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = boolval ? 4 : 5; agtv.val.string.val = outputstr; } else @@ -1856,11 +1886,20 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, break; case AGT_TYPE_NUMERIC: outputstr = OidOutputFunctionCall(outfuncoid, val); + outputstr_len = 0; + numeric_error = false; + for (char *ptr = outputstr; *ptr != '\0'; ptr++) + { + if (*ptr == 'N' || *ptr == 'n') + numeric_error = true; + outputstr_len++; + } + if (key_scalar) { /* always quote keys */ agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = outputstr_len; agtv.val.string.val = outputstr; } else @@ -1870,8 +1909,6 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, * a string. Invalid numeric output will always have an * 'N' or 'n' in it (I think). */ - numeric_error = (strchr(outputstr, 'N') != NULL || - strchr(outputstr, 'n') != NULL); if (!numeric_error) { Datum numd; @@ -1887,7 +1924,7 @@ static void datum_to_agtype(Datum val, bool is_null, agtype_in_state *result, else { agtv.type = AGTV_STRING; - agtv.val.string.len = strlen(outputstr); + agtv.val.string.len = outputstr_len; agtv.val.string.val = outputstr; } } @@ -3450,22 +3487,7 @@ Datum agtype_to_float8(PG_FUNCTION_ARGS) } else if (agtv.type == AGTV_INTEGER) { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - char *string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv.val.int_value))); - bool is_valid = false; - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return null if it was not a invalid float */ - if (!is_valid) - ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), - errmsg("cannot cast to float8, integer value out of range"))); + result = (float8) agtv.val.int_value; } else if (agtv.type == AGTV_NUMERIC) { @@ -5398,8 +5420,7 @@ Datum agtype_typecast_bool(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); break; case AGTV_INTEGER: - d = DirectFunctionCall1(int4_bool, - Int64GetDatum(arg_value->val.int_value)); + d = BoolGetDatum(arg_value->val.int_value != 0); break; /* what was given doesn't cast to a bool */ default: @@ -5451,9 +5472,7 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) switch(arg_value->type) { case AGTV_INTEGER: - d = DirectFunctionCall1(int8out, - Int64GetDatum(arg_value->val.int_value)); - d = DirectFunctionCall1(float8in, d); + d = Float8GetDatum((float8)arg_value->val.int_value); break; case AGTV_FLOAT: /* it is already a float so just return it */ @@ -6576,10 +6595,14 @@ Datum age_toboolean(PG_FUNCTION_ARGS) { if (type == CSTRINGOID) { + int len; + string = DatumGetCString(arg); - if (pg_strcasecmp(string, "true") == 0) + len = strlen(string); + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) result = true; - else if (pg_strcasecmp(string, "false") == 0) + else if (len == 5 && + pg_strncasecmp(string, "false", len) == 0) result = false; else PG_RETURN_NULL(); @@ -6600,7 +6623,12 @@ Datum age_toboolean(PG_FUNCTION_ARGS) } else if (type == INT2OID || type == INT4OID || type == INT8OID) { - result = DatumGetBool(DirectFunctionCall1(int4_bool, arg)); + if (type == INT2OID) + result = DatumGetInt16(arg) != 0; + else if (type == INT4OID) + result = DatumGetInt32(arg) != 0; + else + result = DatumGetInt64(arg) != 0; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -6638,8 +6666,7 @@ Datum age_toboolean(PG_FUNCTION_ARGS) } else if (agtv_value->type == AGTV_INTEGER) { - result = DatumGetBool(DirectFunctionCall1(int4_bool, - Int64GetDatum(agtv_value->val.int_value))); + result = agtv_value->val.int_value != 0; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -6665,7 +6692,6 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) agtype_in_state agis_result; agtype_value *elem; agtype_value bool_elem; - char *string = NULL; int count; int i; @@ -6702,16 +6728,17 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) switch (elem->type) { case AGTV_STRING: - - string = elem->val.string.val; + { + char *string = elem->val.string.val; + int len = elem->val.string.len; - if (pg_strcasecmp(string, "true") == 0) + if (len == 4 && pg_strncasecmp(string, "true", len) == 0) { bool_elem.val.boolean = true; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); } - else if (pg_strcasecmp(string, "false") == 0) + else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) { bool_elem.val.boolean = false; agis_result.res = push_agtype_value(&agis_result.parse_state, @@ -6723,9 +6750,10 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); } - + break; - + } + case AGTV_BOOL: bool_elem.val.boolean = elem->val.boolean; @@ -6736,8 +6764,7 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) case AGTV_INTEGER: - bool_elem.val.boolean = DatumGetBool(DirectFunctionCall1(int4_bool, - Int64GetDatum(elem->val.int_value))); + bool_elem.val.boolean = elem->val.int_value != 0; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &bool_elem); @@ -6787,20 +6814,7 @@ Datum age_tofloat(PG_FUNCTION_ARGS) else if (type == INT4OID) result = (float8) DatumGetInt32(arg); else if (type == INT8OID) - { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - string = DatumGetCString(DirectFunctionCall1(int8out, arg)); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - /* return null if it was not a invalid float */ - if (!is_valid) - PG_RETURN_NULL(); - } + result = (float8) DatumGetInt64(arg); else if (type == FLOAT4OID) result = (float8) DatumGetFloat4(arg); else if (type == FLOAT8OID) @@ -6841,15 +6855,7 @@ Datum age_tofloat(PG_FUNCTION_ARGS) if (agtv_value->type == AGTV_INTEGER) { - /* get the string representation of the integer */ - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - /* return null if it was an invalid float */ - if (!is_valid) - PG_RETURN_NULL(); + result = (float8) agtv_value->val.int_value; } else if (agtv_value->type == AGTV_FLOAT) result = agtv_value->val.float_value; @@ -6863,6 +6869,7 @@ Datum age_tofloat(PG_FUNCTION_ARGS) agtv_value->val.string.len); result = float8in_internal_null(string, NULL, "double precision", string, &is_valid); + pfree(string); if (!is_valid) PG_RETURN_NULL(); } @@ -6890,12 +6897,8 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) agtype_in_state agis_result; agtype_value *elem; agtype_value float_elem; - char *string = NULL; int count; int i; - bool is_valid = false; - float8 float_num; - char buffer[64]; /* check for null */ if (PG_ARGISNULL(0)) @@ -6936,38 +6939,46 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) switch (elem->type) { case AGTV_STRING: + { + bool is_valid = false; + char *string; + + string = pnstrdup(elem->val.string.val, elem->val.string.len); + float_elem.val.float_value = + float8in_internal_null(string, NULL, "double precision", + string, &is_valid); + pfree(string); - string = elem->val.string.val; - if (atof(string)) + if (is_valid) { float_elem.type = AGTV_FLOAT; - float_elem.val.float_value = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); } else { float_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); } break; + } case AGTV_FLOAT: float_elem.type = AGTV_FLOAT; - float_num = elem->val.float_value; - sprintf(buffer, "%f", float_num); - string = buffer; - float_elem.val.float_value = float8in_internal_null(string, NULL, "double precision", string, &is_valid); - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + float_elem.val.float_value = elem->val.float_value; + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); break; default: float_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &float_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &float_elem); break; } @@ -7065,9 +7076,6 @@ Datum age_tointeger(PG_FUNCTION_ARGS) * If it isn't an integer string, try converting it as a float * string. */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - if (*endptr != '\0') { float8 f; @@ -7164,11 +7172,14 @@ Datum age_tointeger(PG_FUNCTION_ARGS) if (!is_valid || isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + pfree(string); PG_RETURN_NULL(); } result = (int64) f; } + + pfree(string); } else { @@ -7198,10 +7209,6 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) agtype_value integer_elem; int count; int i; - char *string = NULL; - int integer_num; - float float_num; - int is_float; /* check for null */ if (PG_ARGISNULL(0)) @@ -7241,62 +7248,80 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) switch (elem->type) { case AGTV_STRING: + { + char *string; + char *endptr; + int64 value; - string = elem->val.string.val; - integer_elem.type = AGTV_INTEGER; - integer_elem.val.int_value = atoi(string); + string = pnstrdup(elem->val.string.val, elem->val.string.len); + value = strtoi64(string, &endptr, 10); - if (*string == '+' || *string == '-' || (*string >= '0' && *string <= '9')) + if (*endptr != '\0') { - is_float = 1; - while (*(++string)) + bool is_valid = false; + float8 f; + + f = float8in_internal_null(string, NULL, "double precision", + string, &is_valid); + if (!is_valid || isnan(f) || isinf(f) || + f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { - if(!(*string >= '0' && *string <= '9')) - { - if(*string == '.' && is_float) - { - is_float--; - } - else - { - integer_elem.type = AGTV_NULL; - break; - } - } + integer_elem.type = AGTV_NULL; + } + else + { + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = (int64)f; } } else { - - integer_elem.type = AGTV_NULL; + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = value; } - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + pfree(string); + + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; + } case AGTV_FLOAT: + { + float8 f = elem->val.float_value; - integer_elem.type = AGTV_INTEGER; - float_num = elem->val.float_value; - integer_elem.val.int_value = (int)float_num; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + if (isnan(f) || isinf(f) || + f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) + { + integer_elem.type = AGTV_NULL; + } + else + { + integer_elem.type = AGTV_INTEGER; + integer_elem.val.int_value = (int64)f; + } + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; + } case AGTV_INTEGER: integer_elem.type = AGTV_INTEGER; - integer_num = elem->val.int_value; - integer_elem.val.int_value = integer_num; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + integer_elem.val.int_value = elem->val.int_value; + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; default: integer_elem.type = AGTV_NULL; - agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &integer_elem); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &integer_elem); break; } @@ -7660,6 +7685,15 @@ Datum age_tostring(PG_FUNCTION_ARGS) * Helper function to take any valid type and convert it to an agtype string. * Returns NULL for NULL input. */ +static char *int64_to_palloc_string(int64 value, int *string_len) +{ + char *string = palloc(24); + + *string_len = pg_lltoa(value, string); + + return string; +} + static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { agtype_value *agtv_result = NULL; @@ -7688,29 +7722,32 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { if (type == INT2OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum((int64) DatumGetInt16(arg)))); + string = int64_to_palloc_string((int64) DatumGetInt16(arg), + &string_len); } else if (type == INT4OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum((int64) DatumGetInt32(arg)))); + string = int64_to_palloc_string((int64) DatumGetInt32(arg), + &string_len); } else if (type == INT8OID) { - string = DatumGetCString(DirectFunctionCall1(int8out, arg)); + string = int64_to_palloc_string(DatumGetInt64(arg), &string_len); } else if (type == FLOAT4OID) { - string = DatumGetCString(DirectFunctionCall1(float8out, arg)); + string = float8out_internal((float8)DatumGetFloat4(arg)); + string_len = strlen(string); } else if (type == FLOAT8OID) { - string = DatumGetCString(DirectFunctionCall1(float8out, arg)); + string = float8out_internal(DatumGetFloat8(arg)); + string_len = strlen(string); } else if (type == NUMERICOID) { string = DatumGetCString(DirectFunctionCall1(numeric_out, arg)); + string_len = strlen(string); } else if (type == CSTRINGOID) { @@ -7725,7 +7762,10 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else if (type == BOOLOID) { - string = DatumGetBool(arg) ? "true" : "false"; + bool value = DatumGetBool(arg); + + string = value ? "true" : "false"; + string_len = value ? 4 : 5; } else if (type == REGTYPEOID) { @@ -7762,13 +7802,13 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else if (agtv_value->type == AGTV_INTEGER) { - string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); + string = int64_to_palloc_string(agtv_value->val.int_value, + &string_len); } else if (agtv_value->type == AGTV_FLOAT) { - string = DatumGetCString(DirectFunctionCall1(float8out, - Float8GetDatum(agtv_value->val.float_value))); + string = float8out_internal(agtv_value->val.float_value); + string_len = strlen(string); } else if (agtv_value->type == AGTV_STRING) { @@ -7779,10 +7819,12 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) { string = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(agtv_value->val.numeric))); + string_len = strlen(string); } else if (agtv_value->type == AGTV_BOOL) { - string = (agtv_value->val.boolean) ? "true" : "false"; + string = agtv_value->val.boolean ? "true" : "false"; + string_len = agtv_value->val.boolean ? 4 : 5; } else { @@ -7874,26 +7916,32 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) break; case AGTV_FLOAT: + { + int len; - sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value); - string_elem.val.string.val = pstrdup(buffer); - string_elem.val.string.len = strlen(buffer); + len = sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value); + string_elem.val.string.val = pnstrdup(buffer, len); + string_elem.val.string.len = len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; + } case AGTV_INTEGER: + { + int len; - sprintf(buffer, "%ld", elem->val.int_value); - string_elem.val.string.val = pstrdup(buffer); - string_elem.val.string.len = strlen(buffer); + len = sprintf(buffer, "%ld", elem->val.int_value); + string_elem.val.string.val = pnstrdup(buffer, len); + string_elem.val.string.len = len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); break; + } default: @@ -9092,23 +9140,19 @@ Datum age_split(PG_FUNCTION_ARGS) /* add the values */ for (i = 0; i < nelements; i++) { + text *elem = DatumGetTextPP(elements[i]); char *string; int string_len; - char *string_copy; agtype_value agtv_string; Datum d; /* get the string element from the array */ - string = VARDATA(elements[i]); - string_len = VARSIZE(elements[i]) - VARHDRSZ; - - /* make a copy */ - string_copy = palloc(string_len); - memcpy(string_copy, string, string_len); + string = VARDATA_ANY(elem); + string_len = VARSIZE_ANY_EXHDR(elem); /* build the agtype string */ agtv_string.type = AGTV_STRING; - agtv_string.val.string.val = string_copy; + agtv_string.val.string.val = string; agtv_string.val.string.len = string_len; /* get the datum */ @@ -9251,22 +9295,7 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, else if (type == INT4OID) result = (float8) DatumGetInt32(arg); else if (type == INT8OID) - { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - char *string = DatumGetCString(DirectFunctionCall1(int8out, arg)); - bool is_valid = false; - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return 0 if it was an invalid float */ - if (!is_valid) - return 0; - } + result = (float8) DatumGetInt64(arg); else if (type == FLOAT4OID) result = (float8) DatumGetFloat4(arg); else if (type == FLOAT8OID) @@ -9300,21 +9329,7 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, if (agtv_value->type == AGTV_INTEGER) { - /* - * Get the string representation of the integer because it could be - * too large to fit in a float. Let the float routine determine - * what to do with it. - */ - bool is_valid = false; - char *string = DatumGetCString(DirectFunctionCall1(int8out, - Int64GetDatum(agtv_value->val.int_value))); - /* turn it into a float */ - result = float8in_internal_null(string, NULL, "double precision", - string, &is_valid); - - /* return null if it was not a valid float */ - if (!is_valid) - return 0; + result = (float8) agtv_value->val.int_value; } else if (agtv_value->type == AGTV_FLOAT) result = agtv_value->val.float_value; diff --git a/src/backend/utils/adt/agtype_gin.c b/src/backend/utils/adt/agtype_gin.c index 91ed15fc6..d57b516dd 100644 --- a/src/backend/utils/adt/agtype_gin.c +++ b/src/backend/utils/adt/agtype_gin.c @@ -519,14 +519,12 @@ static Datum make_scalar_key(const agtype_value *scalarVal, bool is_key) break; case AGTV_INTEGER: { - char *result; + int len; Assert(!is_key); - pg_lltoa(scalarVal->val.int_value, buf); - - result = pstrdup(buf); - item = make_text_key(AGT_GIN_FLAG_NUM, result, strlen(result)); + len = pg_lltoa(scalarVal->val.int_value, buf); + item = make_text_key(AGT_GIN_FLAG_NUM, buf, len); break; } case AGTV_FLOAT: diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index 9b13cf6a9..b44a43fcc 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -29,6 +29,7 @@ #include "utils/agtype.h" #include "utils/datum.h" #include "utils/builtins.h" +#include "utils/float.h" static agtype *agtype_concat_impl(agtype *agt1, agtype *agt2); static agtype_value *iterator_concat(agtype_iterator **it1, @@ -62,24 +63,19 @@ static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, static char *get_string_from_agtype_value(agtype_value *agtv, int *length) { - Datum number; char *string; switch (agtv->type) { case AGTV_INTEGER: - number = DirectFunctionCall1(int8out, - Int64GetDatum(agtv->val.int_value)); - string = DatumGetCString(number); - *length = strlen(string); + string = palloc(24); + *length = pg_lltoa(agtv->val.int_value, string); return string; case AGTV_FLOAT: - number = DirectFunctionCall1(float8out, - Float8GetDatum(agtv->val.float_value)); - string = DatumGetCString(number); + string = float8out_internal(agtv->val.float_value); *length = strlen(string); - if (is_decimal_needed(string)) + if (is_decimal_needed_len(string, *length)) { char *str = palloc(*length + 2); memcpy(str, string, *length); diff --git a/src/backend/utils/adt/graphid.c b/src/backend/utils/adt/graphid.c index f6c013678..43777dfb1 100644 --- a/src/backend/utils/adt/graphid.c +++ b/src/backend/utils/adt/graphid.c @@ -120,9 +120,10 @@ Datum graphid_out(PG_FUNCTION_ARGS) graphid gid = AG_GETARG_GRAPHID(0); char buf[32]; /* greater than MAXINT8LEN+1 */ char *out; + int len; - pg_lltoa(gid, buf); - out = pstrdup(buf); + len = pg_lltoa(gid, buf); + out = pnstrdup(buf, len); PG_RETURN_CSTRING(out); } diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 9c72a6ecb..5efa4c215 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -1050,8 +1050,8 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, { if (cached_labels[i] != NULL && cached_graphs[i] == graph && - namestrcmp(&cached_names[i], name) == 0 && - cached_generations[i] == current_generation) + cached_generations[i] == current_generation && + namestrcmp(&cached_names[i], name) == 0) { return cached_labels[i]; } @@ -1569,6 +1569,12 @@ static void fill_label_cache_data(label_cache_data *cache_data, value = heap_getattr(tuple, Anum_ag_label_relation, tuple_desc, &is_null); Assert(!is_null); cache_data->relation = DatumGetObjectId(value); + { + char *relname = get_rel_name(cache_data->relation); + + namestrcpy(&cache_data->relation_name, relname); + pfree(relname); + } /* ag_label.seq_name */ value = heap_getattr(tuple, Anum_ag_label_seq_name, tuple_desc, &is_null); Assert(!is_null); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index dc028ef15..7f35a22ad 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -39,7 +39,8 @@ #include "utils/load/age_load.h" #include "utils/ag_cache.h" -static agtype_value *csv_value_to_agtype_value(char *csv_val); +static agtype_value *csv_value_to_agtype_value(const char *csv_val, + size_t csv_len); static agtype_value *trimmed_field_to_string_agtype_value(const char *field); static const char *trimmed_string_span(const char *str, size_t *len); static Oid get_or_create_graph(const Name graph_name); @@ -230,40 +231,39 @@ agtype *create_empty_agtype(void) * json in order to be parsed into an agtype_value of appropriate type. * Finally, agtype_value_from_cstring() is called for parsing. */ -static agtype_value *csv_value_to_agtype_value(char *csv_val) +static agtype_value *csv_value_to_agtype_value(const char *csv_val, + size_t csv_len) { char *new_csv_val; + size_t new_csv_len; agtype_value *res; /* Handle NULL or empty input - return null agtype value */ - if (csv_val == NULL || csv_val[0] == '\0') + if (csv_val == NULL || csv_len == 0) { res = palloc(sizeof(agtype_value)); res->type = AGTV_NULL; return res; } - if (!json_validate(cstring_to_text(csv_val), false, false)) + if (!json_validate(cstring_to_text_with_len(csv_val, csv_len), false, false)) { /* wrap the string with double-quote */ - int oldlen; - int newlen; - - oldlen = strlen(csv_val); - newlen = oldlen + 2; /* +2 for double-quotes */ - new_csv_val = (char *)palloc(sizeof(char) * (newlen + 1)); + new_csv_len = csv_len + 2; /* +2 for double-quotes */ + new_csv_val = (char *)palloc(sizeof(char) * (new_csv_len + 1)); new_csv_val[0] = '"'; - strncpy(&new_csv_val[1], csv_val, oldlen); - new_csv_val[oldlen + 1] = '"'; - new_csv_val[oldlen + 2] = '\0'; + memcpy(&new_csv_val[1], csv_val, csv_len); + new_csv_val[csv_len + 1] = '"'; + new_csv_val[csv_len + 2] = '\0'; } else { - new_csv_val = csv_val; + new_csv_val = (char *)csv_val; + new_csv_len = csv_len; } - res = agtype_value_from_cstring(new_csv_val, strlen(new_csv_val)); + res = agtype_value_from_cstring(new_csv_val, new_csv_len); /* extract from top-level row scalar array */ if (res->type == AGTV_ARRAY && res->val.array.raw_scalar) @@ -330,11 +330,13 @@ agtype *create_agtype_from_list(char **header, char **fields, size_t fields_len, if (load_as_agtype) { - char *trimmed_value; + const char *trimmed_value; + size_t trimmed_len; /* Trim whitespace from field value */ - trimmed_value = trim_whitespace(fields[i]); - value_agtype = csv_value_to_agtype_value(trimmed_value); + trimmed_value = trimmed_string_span(fields[i], &trimmed_len); + value_agtype = csv_value_to_agtype_value(trimmed_value, + trimmed_len); } else { @@ -393,11 +395,13 @@ agtype* create_agtype_from_list_i(char **header, char **fields, if (load_as_agtype) { - char *trimmed_value; + const char *trimmed_value; + size_t trimmed_len; /* Trim whitespace from field value */ - trimmed_value = trim_whitespace(fields[i]); - value_agtype = csv_value_to_agtype_value(trimmed_value); + trimmed_value = trimmed_string_span(fields[i], &trimmed_len); + value_agtype = csv_value_to_agtype_value(trimmed_value, + trimmed_len); } else { diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 2ff01c2c4..49826741c 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -36,6 +36,7 @@ typedef struct label_cache_data int32 id; char kind; Oid relation; + NameData relation_name; NameData seq_name; } label_cache_data; diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index eaa37288c..f89e0d816 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -626,6 +626,7 @@ Datum boolean_to_agtype(bool b); void uniqueify_agtype_object(agtype_value *object); char *agtype_value_type_to_string(enum agtype_value_type type); bool is_decimal_needed(char *numstr); +bool is_decimal_needed_len(const char *numstr, int len); int compare_agtype_scalar_values(agtype_value *a, agtype_value *b); agtype_value *alter_property_value(agtype_value *properties, char *var_name, agtype *new_v, bool remove_property); From d91f029193aad61fb9d394cda54f0aff9e5ce911 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:38 +0900 Subject: [PATCH 10/15] Track real DML graph mutations Avoid graph invalidation and metadata serialization when a DML clause does not actually mutate graph data. The combined DML changes lazily create DELETE tracking, record actual SET, CREATE, and MERGE mutations, reuse graph-load snapshots, load graph labels with one catalog scan, cache SET path update attributes and indexes, hash MERGE created paths, cache DELETE item positions, and trim path rebuild overhead. --- src/backend/executor/cypher_create.c | 18 +- src/backend/executor/cypher_delete.c | 149 +++++++++++---- src/backend/executor/cypher_merge.c | 170 ++++++++++++----- src/backend/executor/cypher_set.c | 233 ++++++++++++++++------- src/backend/nodes/cypher_copyfuncs.c | 8 + src/backend/nodes/cypher_readfuncs.c | 8 + src/backend/optimizer/cypher_pathnode.c | 16 +- src/backend/optimizer/cypher_paths.c | 75 ++++++-- src/backend/parser/cypher_clause.c | 86 +++++---- src/backend/utils/adt/age_global_graph.c | 138 ++++++++------ src/backend/utils/adt/agtype.c | 31 ++- src/backend/utils/adt/agtype_ops.c | 20 +- src/include/executor/cypher_utils.h | 12 +- src/include/nodes/cypher_nodes.h | 6 + 14 files changed, 672 insertions(+), 298 deletions(-) diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 4814f775a..5fd2f5256 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -188,6 +188,7 @@ static void process_pattern(cypher_create_custom_scan_state *css) scantuple->tts_isnull[path->path_attr_num - 1] = false; } + list_free(css->path_values); css->path_values = NIL; } } @@ -200,7 +201,8 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) ExprContext *econtext = css->css.ss.ps.ps_ExprContext; TupleTableSlot *slot; bool terminal = CYPHER_CLAUSE_IS_TERMINAL(css->flags); - bool used = false; + + css->graph_mutated = false; /* * If the CREATE clause was the final cypher clause written then we aren't @@ -227,23 +229,13 @@ static TupleTableSlot *exec_cypher_create(CustomScanState *node) process_pattern(css); - /* - * This may not be necessary. If we have an empty pattern, nothing was - * inserted and the current command Id was not used. So, only flag it - * if there is a non empty pattern. - */ - if (css->pattern != NIL) - { - /* the current command Id has been used */ - used = true; - } } while (terminal); /* * If the current command Id wasn't used, nothing was inserted and we're * done. */ - if (!used) + if (!css->graph_mutated) { return NULL; } @@ -427,6 +419,7 @@ static void create_edge(cypher_create_custom_scan_state *css, /* Insert the new edge */ insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* restore the old result relation info */ estate->es_result_relations = old_estate_es_result_relations; @@ -514,6 +507,7 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, /* Insert the new vertex */ insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* restore the old result relation info */ estate->es_result_relations = old_estate_es_result_relations; diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 763b128d7..552a4384a 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -49,9 +49,13 @@ static void ensure_detach_delete_rls(CustomScanState *node, static agtype_value *extract_entity(CustomScanState *node, TupleTableSlot *scanTupleSlot, int entity_position); -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, +static bool delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple); static void init_delete_caches(cypher_delete_custom_scan_state *css); +static HTAB *create_delete_vertex_htab(void); +static int *ensure_delete_item_positions(EState *estate, + cypher_delete_information *delete_data, + int *delete_item_count); const CustomExecMethods cypher_delete_exec_methods = {DELETE_SCAN_STATE_NAME, begin_cypher_delete, @@ -79,7 +83,6 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; Plan *subplan; - HASHCTL hashctl; Assert(list_length(css->cs->custom_plans) == 1); @@ -102,15 +105,6 @@ static void begin_cypher_delete(CustomScanState *node, EState *estate, ExecAssignProjectionInfo(&node->ss.ps, tupdesc); } - /* init vertex_id_htab */ - MemSet(&hashctl, 0, sizeof(hashctl)); - hashctl.keysize = sizeof(graphid); - hashctl.entrysize = - sizeof(graphid); /* entries are not used, but entrysize must >= keysize */ - hashctl.hash = tag_hash; - css->vertex_id_htab = hash_create(DELETE_VERTEX_HTAB_NAME, - DELETE_VERTEX_HTAB_INITIAL_SIZE, - &hashctl, HASH_ELEM | HASH_FUNCTION); init_delete_caches(css); /* @@ -201,7 +195,10 @@ static void end_cypher_delete(CustomScanState *node) check_for_connected_edges(node); /* invalidate VLE cache — graph was mutated */ - increment_graph_version(css->delete_data->graph_oid); + if (css->graph_mutated) + { + increment_graph_version(css->delete_data->graph_oid); + } if (css->qual_cache != NULL) { @@ -221,7 +218,11 @@ static void end_cypher_delete(CustomScanState *node) css->result_rel_info_cache = NULL; } - hash_destroy(((cypher_delete_custom_scan_state *)node)->vertex_id_htab); + if (css->vertex_id_htab != NULL) + { + hash_destroy(css->vertex_id_htab); + css->vertex_id_htab = NULL; + } ExecEndNode(node->ss.ps.lefttree); } @@ -249,6 +250,61 @@ static void init_delete_caches(cypher_delete_custom_scan_state *css) create_entity_result_rel_info_cache("delete_result_rel_info_cache"); } +static HTAB *create_delete_vertex_htab(void) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(graphid); + hashctl.entrysize = + sizeof(graphid); /* entries are not used, but entrysize must >= keysize */ + hashctl.hash = tag_hash; + + return hash_create(DELETE_VERTEX_HTAB_NAME, + DELETE_VERTEX_HTAB_INITIAL_SIZE, + &hashctl, HASH_ELEM | HASH_FUNCTION); +} + +static int *ensure_delete_item_positions(EState *estate, + cypher_delete_information *delete_data, + int *delete_item_count) +{ + ListCell *lc; + int count; + int index = 0; + + if (delete_data->delete_item_positions_valid) + { + *delete_item_count = delete_data->delete_item_count; + return delete_data->delete_item_positions; + } + + count = list_length(delete_data->delete_items); + + if (delete_data->delete_item_positions != NULL) + { + pfree(delete_data->delete_item_positions); + } + + delete_data->delete_item_positions = count > 0 ? + MemoryContextAlloc(estate->es_query_cxt, sizeof(int) * count) : NULL; + delete_data->delete_item_count = count; + + foreach(lc, delete_data->delete_items) + { + cypher_delete_item *item = lfirst(lc); + + delete_data->delete_item_positions[index++] = + item->entity_position->ival; + } + + Assert(index == count); + delete_data->delete_item_positions_valid = true; + + *delete_item_count = count; + return delete_data->delete_item_positions; +} + /* * Used for rewinding the scan state and reprocessing the results. * @@ -328,7 +384,7 @@ static agtype_value *extract_entity(CustomScanState *node, * Try and delete the entity that is describe by the HeapTuple in the table * described by the resultRelInfo. */ -static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, +static bool delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple) { ResultRelInfo **saved_resultRels; @@ -338,6 +394,7 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, TM_Result delete_result; Buffer buffer; CommandId current_cid; + bool deleted = false; /* Find the physical tuple, this variable is coming from */ saved_resultRels = estate->es_result_relations; @@ -371,6 +428,7 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, switch (delete_result) { case TM_Ok: + deleted = true; break; case TM_SelfModified: ereport( @@ -411,6 +469,8 @@ static void delete_entity(EState *estate, ResultRelInfo *resultRelInfo, ReleaseBuffer(buffer); estate->es_result_relations = saved_resultRels; + + return deleted; } /* @@ -421,23 +481,26 @@ static void process_delete_list(CustomScanState *node) { cypher_delete_custom_scan_state *css = (cypher_delete_custom_scan_state *)node; - ListCell *lc; ExprContext *econtext = css->css.ss.ps.ps_ExprContext; TupleTableSlot *scanTupleSlot = econtext->ecxt_scantuple; EState *estate = node->ss.ps.state; HTAB *qual_cache = css->qual_cache; HTAB *index_cache = css->index_cache; + int delete_item_count; + int *delete_item_positions; + int i; + + delete_item_positions = ensure_delete_item_positions( + estate, css->delete_data, &delete_item_count); - foreach(lc, css->delete_data->delete_items) + for (i = 0; i < delete_item_count; i++) { - cypher_delete_item *item; agtype_value *original_entity_value, *id; ScanKeyData scan_keys[1]; TableScanDesc scan_desc = NULL; ResultRelInfo *resultRelInfo; HeapTuple heap_tuple = NULL; label_cache_data *label_cache; - Integer *pos; int entity_position; Oid relid; Relation rel; @@ -452,10 +515,7 @@ static void process_delete_list(CustomScanState *node) RLSCacheEntry *rls_entry; bool found_rls_entry; - item = lfirst(lc); - - pos = item->entity_position; - entity_position = pos->ival; + entity_position = delete_item_positions[i]; /* skip if the entity is null */ if (scanTupleSlot->tts_isnull[entity_position - 1]) @@ -601,20 +661,28 @@ static void process_delete_list(CustomScanState *node) if (passed_rls) { - /* - * For vertices, we insert the vertex ID in the hashtable - * vertex_id_htab. This hashtable is used later to process - * connected edges. - */ - if (original_entity_value->type == AGTV_VERTEX) + /* At this point, we are ready to delete the node/vertex. */ + if (delete_entity(estate, resultRelInfo, heap_tuple)) { - bool found; - hash_search(css->vertex_id_htab, (void *)&(id->val.int_value), - HASH_ENTER, &found); - } + css->graph_mutated = true; + /* + * For vertices, store successfully deleted IDs for the + * later connected-edge pass. + */ + if (original_entity_value->type == AGTV_VERTEX) + { + bool found; - /* At this point, we are ready to delete the node/vertex. */ - delete_entity(estate, resultRelInfo, heap_tuple); + if (css->vertex_id_htab == NULL) + { + css->vertex_id_htab = create_delete_vertex_htab(); + } + + hash_search(css->vertex_id_htab, + (void *)&(id->val.int_value), + HASH_ENTER, &found); + } + } } if (shouldFree) @@ -745,7 +813,10 @@ static void process_edges_by_index(Oid index_oid, } tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree); - delete_entity(estate, resultRelInfo, tuple); + if (delete_entity(estate, resultRelInfo, tuple)) + { + css->graph_mutated = true; + } if (shouldFree) { @@ -801,7 +872,8 @@ static void check_for_connected_edges(CustomScanState *node) (cypher_delete_custom_scan_state *)node; EState *estate = css->css.ss.ps.state; - if (hash_get_num_entries(css->vertex_id_htab) == 0) + if (css->vertex_id_htab == NULL || + hash_get_num_entries(css->vertex_id_htab) == 0) { return; } @@ -978,7 +1050,10 @@ static void check_for_connected_edges(CustomScanState *node) } } - delete_entity(estate, resultRelInfo, tuple); + if (delete_entity(estate, resultRelInfo, tuple)) + { + css->graph_mutated = true; + } } else { diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 29b65dd5e..1bd150551 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -58,10 +58,17 @@ typedef struct path_entry typedef struct created_path { struct created_path *next; /* next link in linked list of path_entrys */ + struct created_path *hash_next; /* next link in the path_hash bucket */ struct path_entry **entry; /* path_entry array for this link */ uint32 path_hash; /* cheap precheck before full path compare */ } created_path; +typedef struct created_path_hash_entry +{ + uint32 path_hash; + created_path *paths; +} created_path_hash_entry; + static void begin_cypher_merge(CustomScanState *node, EState *estate, int eflags); static TupleTableSlot *exec_cypher_merge(CustomScanState *node); @@ -83,6 +90,9 @@ static void process_path(cypher_merge_custom_scan_state *css, path_entry **path_array, bool should_insert); static void mark_tts_isnull(TupleTableSlot *slot); static void mark_scan_slot_valid(TupleTableSlot *slot); +static void apply_merge_update_list(cypher_merge_custom_scan_state *css, + cypher_update_information *set_info, + int num_set_items); const CustomExecMethods cypher_merge_exec_methods = {MERGE_SCAN_STATE_NAME, begin_cypher_merge, @@ -99,8 +109,21 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, static path_entry **find_duplicate_path(CustomScanState *node, path_entry **path_array, uint32 path_hash); +static void remember_created_path(cypher_merge_custom_scan_state *css, + path_entry **path_array, + uint32 path_hash); static void free_path_entry_array(path_entry **path_array, int length); +static void apply_merge_update_list(cypher_merge_custom_scan_state *css, + cypher_update_information *set_info, + int num_set_items) +{ + if (apply_update_list(&css->css, set_info, num_set_items)) + { + css->graph_mutated = true; + } +} + /* * Initializes the MERGE Execution Node at the beginning of the execution * phase. @@ -329,6 +352,9 @@ static void process_path(cypher_merge_custom_scan_state *css, scantuple->tts_isnull[tuple_position] = false; } } + + list_free(css->path_values); + css->path_values = NIL; } /* @@ -364,8 +390,8 @@ static void process_simple_merge(CustomScanState *node) if (css->on_create_set_info) { mark_scan_slot_valid(econtext->ecxt_scantuple); - apply_update_list(&css->css, css->on_create_set_info, - css->on_create_set_item_count); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); } } else @@ -378,8 +404,8 @@ static void process_simple_merge(CustomScanState *node) econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } } } @@ -643,7 +669,7 @@ static bool compare_2_paths(path_entry **lhs, path_entry **rhs, int path_length) return true; } -/* helper function to find a duplicate path in the created_paths_list */ +/* helper function to find a duplicate path in the created path cache */ static path_entry **find_duplicate_path(CustomScanState *node, path_entry **path_array, uint32 path_hash) @@ -651,21 +677,38 @@ static path_entry **find_duplicate_path(CustomScanState *node, cypher_merge_custom_scan_state *css = (cypher_merge_custom_scan_state *)node; - /* if the list is NULL just return NULL */ if (css->created_paths_list == NULL) { return NULL; } - /* otherwise, check to see if the path already exists */ + + if (css->created_paths_hash != NULL) + { + created_path_hash_entry *entry; + + entry = hash_search(css->created_paths_hash, &path_hash, HASH_FIND, + NULL); + if (entry == NULL) + { + return NULL; + } + + for (created_path *curr_path = entry->paths; curr_path != NULL; + curr_path = curr_path->hash_next) + { + if (compare_2_paths(path_array, curr_path->entry, + css->path_length)) + { + return curr_path->entry; + } + } + } else { - /* set to the top of the list */ created_path *curr_path = css->created_paths_list; - /* iterate through our list of created paths */ while (curr_path != NULL) { - /* if we have found the entry, return it */ if (curr_path->path_hash == path_hash && compare_2_paths(path_array, curr_path->entry, css->path_length)) @@ -673,15 +716,52 @@ static path_entry **find_duplicate_path(CustomScanState *node, return curr_path->entry; } - /* otherwise, get the next path */ curr_path = curr_path->next; } } - /* if we didn't find it, return NULL */ return NULL; } +static void remember_created_path(cypher_merge_custom_scan_state *css, + path_entry **path_array, + uint32 path_hash) +{ + created_path *new_path; + created_path_hash_entry *entry; + bool found; + + if (css->created_paths_hash == NULL) + { + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(uint32); + hashctl.entrysize = sizeof(created_path_hash_entry); + hashctl.hcxt = css->css.ss.ps.state->es_query_cxt; + css->created_paths_hash = hash_create("merge_created_paths_hash", 32, + &hashctl, + HASH_ELEM | HASH_BLOBS | + HASH_CONTEXT); + } + + new_path = palloc(sizeof(created_path)); + new_path->next = css->created_paths_list; + new_path->entry = path_array; + new_path->path_hash = path_hash; + css->created_paths_list = new_path; + + entry = hash_search(css->created_paths_hash, &path_hash, HASH_ENTER, + &found); + if (!found) + { + entry->paths = NULL; + } + + new_path->hash_next = entry->paths; + entry->paths = new_path; +} + /* * Function that is called mid-execution. This function will call * its subtree in the execution tree, and depending on the results @@ -786,35 +866,31 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) - apply_update_list(&css->css, - css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, + css->on_match_set_info, + css->on_match_set_item_count); } else { - created_path *new_path = palloc(sizeof(created_path)); - - new_path->next = css->created_paths_list; - new_path->entry = prebuilt_path_array; - new_path->path_hash = path_hash; - css->created_paths_list = new_path; + remember_created_path(css, prebuilt_path_array, + path_hash); process_path(css, prebuilt_path_array, true); mark_scan_slot_valid(econtext->ecxt_scantuple); /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, - css->on_create_set_info, - css->on_create_set_item_count); + apply_merge_update_list(css, + css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } /* Project the result and save a copy */ @@ -898,33 +974,29 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON MATCH SET: path was found as duplicate */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } else { - created_path *new_path = palloc(sizeof(created_path)); - - new_path->next = css->created_paths_list; - new_path->entry = prebuilt_path_array; - new_path->path_hash = path_hash; - css->created_paths_list = new_path; + remember_created_path(css, prebuilt_path_array, + path_hash); process_path(css, prebuilt_path_array, true); mark_scan_slot_valid(econtext->ecxt_scantuple); /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info, - css->on_create_set_item_count); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); } } else { /* ON MATCH SET: path already existed from lateral join */ if (css->on_match_set_info) - apply_update_list(&css->css, css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } } while (true); @@ -1003,8 +1075,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) { econtext->ecxt_scantuple = node->ss.ps.lefttree->ps_ProjInfo->pi_exprContext->ecxt_scantuple; - apply_update_list(&css->css, css->on_match_set_info, - css->on_match_set_item_count); + apply_merge_update_list(css, css->on_match_set_info, + css->on_match_set_item_count); } econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); @@ -1082,8 +1154,8 @@ static TupleTableSlot *exec_cypher_merge(CustomScanState *node) /* ON CREATE SET: path was just created */ if (css->on_create_set_info) - apply_update_list(&css->css, css->on_create_set_info, - css->on_create_set_item_count); + apply_merge_update_list(css, css->on_create_set_info, + css->on_create_set_item_count); /* mark the create_new_path flag to true. */ css->created_new_path = true; @@ -1122,8 +1194,8 @@ static void end_cypher_merge(CustomScanState *node) /* increment the command counter */ CommandCounterIncrement(); - /* invalidate VLE cache if merge created anything */ - if (css->created_new_path) + /* invalidate VLE cache if MERGE changed the graph */ + if (css->graph_mutated) { increment_graph_version(css->graph_oid); } @@ -1172,6 +1244,12 @@ static void end_cypher_merge(CustomScanState *node) css->result_rel_info_cache = NULL; } + if (css->created_paths_hash != NULL) + { + hash_destroy(css->created_paths_hash); + css->created_paths_hash = NULL; + } + /* free up our created paths lists */ while (css->created_paths_list != NULL) { @@ -1400,6 +1478,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, css->base_currentCommandId == GetCurrentCommandId(false)) { insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* * Increment the currentCommandId since we processed an update. We @@ -1413,6 +1492,7 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, { insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, css->base_currentCommandId); + css->graph_mutated = true; } /* restore the old result relation info */ @@ -1741,6 +1821,7 @@ static void merge_edge(cypher_merge_custom_scan_state *css, css->base_currentCommandId == GetCurrentCommandId(false)) { insert_entity_tuple(resultRelInfo, elemTupleSlot, estate); + css->graph_mutated = true; /* * Increment the currentCommandId since we processed an update. We @@ -1754,6 +1835,7 @@ static void merge_edge(cypher_merge_custom_scan_state *css, { insert_entity_tuple_cid(resultRelInfo, elemTupleSlot, estate, css->base_currentCommandId); + css->graph_mutated = true; } /* restore the old result relation info */ diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index b394843c2..0f4f904ce 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -37,7 +37,7 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node); static void end_cypher_set(CustomScanState *node); static void rescan_cypher_set(CustomScanState *node); -static void process_update_list(CustomScanState *node); +static bool process_update_list(CustomScanState *node); static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate, HeapTuple old_tuple); @@ -48,6 +48,13 @@ static void init_update_caches(HTAB **qual_cache, HTAB **index_cache, static void init_update_lookup_caches(HTAB **qual_cache, HTAB **index_cache, const char *qual_name, const char *index_name); +static void ensure_path_update_attrs(CustomScanState *node, + TupleDesc tupleDescriptor, + int **attrs, int *attr_count); +static int *ensure_last_update_indexes(EState *estate, + cypher_update_information *set_info, + int num_set_items, + int natts); const CustomExecMethods cypher_set_exec_methods = {SET_SCAN_STATE_NAME, begin_cypher_set, @@ -285,19 +292,22 @@ static HeapTuple update_entity_tuple(ResultRelInfo *resultRelInfo, * 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) +static bool process_all_tuples(CustomScanState *node) { cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; TupleTableSlot *slot; EState *estate = css->css.ss.ps.state; + bool graph_mutated = false; do { - process_update_list(node); + graph_mutated |= process_update_list(node); Decrement_Estate_CommandId(estate) slot = ExecProcNode(node->ss.ps.lefttree); Increment_Estate_CommandId(estate) } while (!TupIsNull(slot)); + + return graph_mutated; } /* @@ -347,22 +357,13 @@ static agtype_value *replace_entity_in_path(agtype_value *path, graphid updated_id, agtype *updated_entity) { - agtype_iterator *it; - agtype_iterator_token tok = WAGT_DONE; agtype_parse_state *parse_state = NULL; - agtype_value *r; agtype_value *parsed_agtype_value = NULL; - agtype *prop_agtype; + agtype_value *updated_entity_value = NULL; int i; - r = palloc(sizeof(agtype_value)); - - prop_agtype = agtype_value_to_agtype(path); - it = agtype_iterator_init(&prop_agtype->root); - tok = agtype_iterator_next(&it, r, true); - - parsed_agtype_value = push_agtype_value(&parse_state, tok, - tok < WAGT_BEGIN_ARRAY ? r : NULL); + parsed_agtype_value = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, + NULL); /* Iterate through the path, replace entities as necessary. */ for (i = 0; i < path->val.array.num_elems; i++) @@ -394,8 +395,15 @@ static agtype_value *replace_entity_in_path(agtype_value *path, */ if (updated_id == id->val.int_value) { + if (updated_entity_value == NULL) + { + updated_entity_value = + get_ith_agtype_value_from_container(&updated_entity->root, + 0); + } + parsed_agtype_value = push_agtype_value(&parse_state, WAGT_ELEM, - get_ith_agtype_value_from_container(&updated_entity->root, 0)); + updated_entity_value); } else { @@ -423,27 +431,28 @@ static void update_all_paths(CustomScanState *node, graphid id, TupleDesc tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; Datum *values = scanTupleSlot->tts_values; bool *isnull = scanTupleSlot->tts_isnull; - int natts = tupleDescriptor->natts; + int *attrs = NULL; + int attr_count = 0; int i; - for (i = 0; i < natts; i++) + ensure_path_update_attrs(node, tupleDescriptor, &attrs, &attr_count); + if (attr_count == 0) + { + return; + } + + for (i = 0; i < attr_count; i++) { agtype *original_entity; agtype_value *original_entity_value; + int attr = attrs[i]; - /* skip nulls */ - if (TupleDescAttr(tupleDescriptor, i)->atttypid != AGTYPEOID) + if (isnull[attr]) { continue; } - /* skip non agtype values */ - if (isnull[i]) - { - continue; - } - - original_entity = DATUM_GET_AGTYPE_P(values[i]); + original_entity = DATUM_GET_AGTYPE_P(values[attr]); /* if the value is not a scalar type, its not a path */ if (!AGTYPE_CONTAINER_IS_SCALAR(&original_entity->root)) @@ -462,18 +471,88 @@ static void update_all_paths(CustomScanState *node, graphid id, /* the path does contain the entity replace with the new entity. */ agtype_value *new_path = replace_entity_in_path(original_entity_value, id, updated_entity); - values[i] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); + values[attr] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); } } } } +static void ensure_path_update_attrs(CustomScanState *node, + TupleDesc tupleDescriptor, + int **attrs, int *attr_count) +{ + int **cached_attrs = NULL; + int *cached_count = NULL; + bool *cached_valid = NULL; + int i; + + if (node->methods == &cypher_set_exec_methods) + { + cypher_set_custom_scan_state *css = + (cypher_set_custom_scan_state *)node; + + cached_attrs = &css->path_update_attrs; + cached_count = &css->path_update_attr_count; + cached_valid = &css->path_update_attrs_valid; + } + else if (node->methods == &cypher_merge_exec_methods) + { + cypher_merge_custom_scan_state *css = + (cypher_merge_custom_scan_state *)node; + + cached_attrs = &css->path_update_attrs; + cached_count = &css->path_update_attr_count; + cached_valid = &css->path_update_attrs_valid; + } + + if (cached_valid == NULL) + { + *attrs = NULL; + *attr_count = 0; + return; + } + + if (!*cached_valid) + { + int count = 0; + + for (i = 0; i < tupleDescriptor->natts; i++) + { + if (TupleDescAttr(tupleDescriptor, i)->atttypid == AGTYPEOID) + { + count++; + } + } + + if (count > 0) + { + int index = 0; + + *cached_attrs = MemoryContextAlloc(node->ss.ps.state->es_query_cxt, + sizeof(int) * count); + for (i = 0; i < tupleDescriptor->natts; i++) + { + if (TupleDescAttr(tupleDescriptor, i)->atttypid == AGTYPEOID) + { + (*cached_attrs)[index++] = i; + } + } + } + + *cached_count = count; + *cached_valid = true; + } + + *attrs = *cached_attrs; + *attr_count = *cached_count; +} + /* * Core SET logic that can be called from any executor (SET, MERGE, etc.). * Takes the CustomScanState for expression context and a * cypher_update_information describing which properties to set. */ -void apply_update_list(CustomScanState *node, +bool apply_update_list(CustomScanState *node, cypher_update_information *set_info, int num_set_items) { @@ -487,6 +566,7 @@ void apply_update_list(CustomScanState *node, HTAB *index_cache = NULL; HTAB *result_rel_info_cache = NULL; bool local_caches = false; + bool graph_mutated = false; Oid graph_oid; if (node->methods == &cypher_set_exec_methods) @@ -545,35 +625,12 @@ void apply_update_list(CustomScanState *node, if (num_set_items <= 0) num_set_items = list_length(set_info->set_items); - /* - * Iterate through the SET items list and store the loop index of each - * 'entity' update. As there is only one entry for each entity, this will - * have the effect of overwriting the previous loop index stored - if this - * 'entity' is used more than once. This will create an array of the last - * loop index for the update of that particular 'entity'. This will allow us - * to correctly update an 'entity' after all other previous updates to that - * 'entity' have been done. - */ if (num_set_items > 1) { - /* allocate an array to hold the last update index of each 'entity' */ - luindex = palloc(sizeof(int) * scanTupleSlot->tts_nvalid); - - foreach (lc, set_info->set_items) - { - cypher_update_item *update_item = NULL; - - update_item = (cypher_update_item *)lfirst(lc); - luindex[update_item->entity_position - 1] = lidx; - - /* increment the loop index */ - lidx++; - } + luindex = ensure_last_update_indexes(estate, set_info, num_set_items, + scanTupleSlot->tts_tupleDescriptor->natts); } - /* reset loop index */ - lidx = 0; - /* iterate through SET set items */ foreach (lc, set_info->set_items) { @@ -935,6 +992,7 @@ void apply_update_list(CustomScanState *node, { heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, original_tuple); + graph_mutated |= HeapTupleIsValid(heap_tuple); } if (shouldFree) @@ -987,6 +1045,7 @@ void apply_update_list(CustomScanState *node, { heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, heap_tuple); + graph_mutated |= HeapTupleIsValid(heap_tuple); } } /* close the ScanDescription */ @@ -1007,15 +1066,55 @@ void apply_update_list(CustomScanState *node, destroy_entity_result_rel_info_cache(result_rel_info_cache); } - /* free our lookup array */ - pfree_if_not_null(luindex); + return graph_mutated; +} + +static int *ensure_last_update_indexes(EState *estate, + cypher_update_information *set_info, + int num_set_items, + int natts) +{ + ListCell *lc; + int lidx = 0; + + if (set_info->last_update_indexes_valid && + set_info->last_update_index_count == natts) + { + return set_info->last_update_indexes; + } + + if (set_info->last_update_indexes != NULL) + { + pfree(set_info->last_update_indexes); + } + + set_info->last_update_indexes = + MemoryContextAlloc(estate->es_query_cxt, sizeof(int) * natts); + set_info->last_update_index_count = natts; + + /* + * Store the last update index for each entity position once per plan + * execution. This used to be rebuilt for every input row. + */ + foreach (lc, set_info->set_items) + { + cypher_update_item *update_item = (cypher_update_item *)lfirst(lc); + + set_info->last_update_indexes[update_item->entity_position - 1] = lidx; + lidx++; + } + + Assert(lidx == num_set_items); + set_info->last_update_indexes_valid = true; + + return set_info->last_update_indexes; } -static void process_update_list(CustomScanState *node) +static bool process_update_list(CustomScanState *node) { cypher_set_custom_scan_state *css = (cypher_set_custom_scan_state *)node; - apply_update_list(node, css->set_list, css->set_item_count); + return apply_update_list(node, css->set_list, css->set_item_count); } static TupleTableSlot *exec_cypher_set(CustomScanState *node) @@ -1043,27 +1142,31 @@ static TupleTableSlot *exec_cypher_set(CustomScanState *node) if (CYPHER_CLAUSE_IS_TERMINAL(css->flags)) { + bool graph_mutated; + estate->es_result_relations = saved_resultRels; - process_all_tuples(node); + graph_mutated = process_all_tuples(node); /* increment the command counter to reflect the updates */ CommandCounterIncrement(); - /* invalidate VLE cache — graph was mutated */ - increment_graph_version(css->graph_oid); + if (graph_mutated) + { + increment_graph_version(css->graph_oid); + } return NULL; } - process_update_list(node); + if (process_update_list(node)) + { + increment_graph_version(css->graph_oid); + } /* increment the command counter to reflect the updates */ CommandCounterIncrement(); - /* invalidate VLE cache — graph was mutated */ - increment_graph_version(css->graph_oid); - estate->es_result_relations = saved_resultRels; econtext->ecxt_scantuple = ExecProject(node->ss.ps.lefttree->ps_ProjInfo); diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index 9747c2603..e40c5b40e 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -123,6 +123,10 @@ void copy_cypher_update_information(ExtensibleNode *newnode, const ExtensibleNod COPY_STRING_FIELD(graph_name); COPY_SCALAR_FIELD(graph_oid); COPY_STRING_FIELD(clause_name); + + extended_newnode->last_update_indexes = NULL; + extended_newnode->last_update_index_count = 0; + extended_newnode->last_update_indexes_valid = false; } /* copy function for cypher_update_item */ @@ -151,6 +155,10 @@ void copy_cypher_delete_information(ExtensibleNode *newnode, const ExtensibleNod COPY_STRING_FIELD(graph_name); COPY_SCALAR_FIELD(graph_oid); COPY_SCALAR_FIELD(detach); + + extended_newnode->delete_item_positions = NULL; + extended_newnode->delete_item_count = 0; + extended_newnode->delete_item_positions_valid = false; } /* copy function for cypher_delete_item */ diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 1d687afc3..00d7d2239 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -253,6 +253,10 @@ void read_cypher_update_information(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_UINT_FIELD(graph_oid); READ_STRING_FIELD(clause_name); + + local_node->last_update_indexes = NULL; + local_node->last_update_index_count = 0; + local_node->last_update_indexes_valid = false; } /* @@ -287,6 +291,10 @@ void read_cypher_delete_information(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_UINT_FIELD(graph_oid); READ_BOOL_FIELD(detach); + + local_node->delete_item_positions = NULL; + local_node->delete_item_count = 0; + local_node->delete_item_positions_valid = false; } /* diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 4ae03d19d..454cece32 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -167,7 +167,7 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private char *serialized_data = NULL; Const *c = NULL; ListCell *lc = NULL; - StringInfo str = makeStringInfo(); + bool changed = false; c = linitial(custom_private); serialized_data = (char *)c->constvalue; @@ -184,14 +184,22 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private if (expr_has_sublink(prop_expr, NULL)) { node->prop_expr = (Expr *) SS_process_sublinks(root, prop_expr, false); + changed = true; } } + if (!changed) + return c; + /* Serialize the information again and return it. */ - outNode(str, (Node *)merge_information); + { + StringInfo str = makeStringInfo(); - return makeConst(INTERNALOID, -1, InvalidOid, str->len, - PointerGetDatum(str->data), false, false); + outNode(str, (Node *)merge_information); + + return makeConst(INTERNALOID, -1, InvalidOid, str->len, + PointerGetDatum(str->data), false, false); + } } /* diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index 71284eccd..bf1335cbe 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -48,7 +48,11 @@ static bool cypher_clause_func_callback_registered = false; static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); -static void init_cypher_clause_function_oids(void); +static void register_cypher_clause_function_oid_callbacks(void); +static Oid get_cypher_create_clause_func_oid(void); +static Oid get_cypher_set_clause_func_oid(void); +static Oid get_cypher_delete_clause_func_oid(void); +static Oid get_cypher_merge_clause_func_oid(void); static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, uint32 hash_value); static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, @@ -124,21 +128,20 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) return CYPHER_CLAUSE_NONE; fe = (FuncExpr *)te->expr; - init_cypher_clause_function_oids(); - if (fe->funcid == cypher_create_clause_func_oid) + if (fe->funcid == get_cypher_create_clause_func_oid()) return CYPHER_CLAUSE_CREATE; - if (fe->funcid == cypher_set_clause_func_oid) + if (fe->funcid == get_cypher_set_clause_func_oid()) return CYPHER_CLAUSE_SET; - if (fe->funcid == cypher_delete_clause_func_oid) + if (fe->funcid == get_cypher_delete_clause_func_oid()) return CYPHER_CLAUSE_DELETE; - if (fe->funcid == cypher_merge_clause_func_oid) + if (fe->funcid == get_cypher_merge_clause_func_oid()) return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; } -static void init_cypher_clause_function_oids(void) +static void register_cypher_clause_function_oid_callbacks(void) { if (!cypher_clause_func_callback_registered) { @@ -150,20 +153,58 @@ static void init_cypher_clause_function_oids(void) (Datum)0); cypher_clause_func_callback_registered = true; } +} + +static Oid get_cypher_create_clause_func_oid(void) +{ + register_cypher_clause_function_oid_callbacks(); + + if (!OidIsValid(cypher_create_clause_func_oid)) + { + cypher_create_clause_func_oid = + get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return cypher_create_clause_func_oid; +} + +static Oid get_cypher_set_clause_func_oid(void) +{ + register_cypher_clause_function_oid_callbacks(); + + if (!OidIsValid(cypher_set_clause_func_oid)) + { + cypher_set_clause_func_oid = + get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return cypher_set_clause_func_oid; +} + +static Oid get_cypher_delete_clause_func_oid(void) +{ + register_cypher_clause_function_oid_callbacks(); + + if (!OidIsValid(cypher_delete_clause_func_oid)) + { + cypher_delete_clause_func_oid = + get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return cypher_delete_clause_func_oid; +} + +static Oid get_cypher_merge_clause_func_oid(void) +{ + register_cypher_clause_function_oid_callbacks(); - if (OidIsValid(cypher_create_clause_func_oid)) + if (!OidIsValid(cypher_merge_clause_func_oid)) { - return; + cypher_merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - cypher_create_clause_func_oid = - get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); - cypher_set_clause_func_oid = - get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); - cypher_delete_clause_func_oid = - get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); - cypher_merge_clause_func_oid = - get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + return cypher_merge_clause_func_oid; } static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 3f783e65e..edbfb32bc 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -355,7 +355,10 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, static List *make_target_list_from_join(ParseState *pstate, RangeTblEntry *rte); static void initialize_clause_function_oid_cache(void); -static Oid get_clause_function_oid(const char *function_name); +static Oid get_create_clause_func_oid(void); +static Oid get_set_clause_func_oid(void); +static Oid get_delete_clause_func_oid(void); +static Oid get_merge_clause_func_oid(void); static Oid get_bool_or_func_oid(void); static Oid get_materialize_vle_edges_func_oid(void); static Oid get_build_path_func_oid(void); @@ -367,7 +370,7 @@ static Oid get_age_properties_func_oid(void); static Node *make_age_properties_func_expr(Node *arg); static void invalidate_clause_function_oids(Datum arg, int cache_id, uint32 hash_value); -static FuncExpr *make_clause_func_expr(char *function_name, +static FuncExpr *make_clause_func_expr(Oid func_oid, Node *clause_information); static void markRelsAsNulledBy(ParseState *pstate, Node *n, int jindex); @@ -1496,7 +1499,7 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, delete_data->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; } - func_expr = make_clause_func_expr(DELETE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_delete_clause_func_oid(), (Node *)delete_data); /* Create the target entry */ @@ -2133,7 +2136,7 @@ static Query *transform_cypher_set(cypher_parsestate *cpstate, set_items_target_list->flags |= CYPHER_CLAUSE_FLAG_TERMINAL; } - func_expr = make_clause_func_expr(SET_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_set_clause_func_oid(), (Node *)set_items_target_list); /* Create the target entry */ @@ -6300,7 +6303,7 @@ static Query *transform_cypher_create(cypher_parsestate *cpstate, } - func_expr = make_clause_func_expr(CREATE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_create_clause_func_oid(), (Node *)target_nodes); /* Create the target entry */ @@ -7458,7 +7461,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, * Creates the function expression that the planner will find and * convert to a MERGE path. */ - func_expr = make_clause_func_expr(MERGE_CLAUSE_FUNCTION_NAME, + func_expr = make_clause_func_expr(get_merge_clause_func_oid(), (Node *)merge_information); /* Create the target entry */ @@ -8329,11 +8332,10 @@ static ParseNamespaceItem *get_namespace_item(ParseState *pstate, * extensible node that represents the metadata that the clause needs to * handle the clause in the execution phase. */ -static FuncExpr *make_clause_func_expr(char *function_name, +static FuncExpr *make_clause_func_expr(Oid func_oid, Node *clause_information) { Const *clause_information_const; - Oid func_oid; FuncExpr *func_expr; StringInfo str = makeStringInfo(); @@ -8352,8 +8354,6 @@ static FuncExpr *make_clause_func_expr(char *function_name, clause_information_const = makeConst(INTERNALOID, -1, InvalidOid, str->len, PointerGetDatum(str->data), false, false); - func_oid = get_clause_function_oid(function_name); - func_expr = makeFuncExpr(func_oid, AGTYPEOID, list_make1(clause_information_const), InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); @@ -8361,7 +8361,7 @@ static FuncExpr *make_clause_func_expr(char *function_name, return func_expr; } -static Oid get_clause_function_oid(const char *function_name) +static Oid get_create_clause_func_oid(void) { initialize_clause_function_oid_cache(); @@ -8369,46 +8369,48 @@ static Oid get_clause_function_oid(const char *function_name) { create_clause_func_oid = get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return create_clause_func_oid; +} + +static Oid get_set_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(set_clause_func_oid)) + { set_clause_func_oid = get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); + } + + return set_clause_func_oid; +} + +static Oid get_delete_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(delete_clause_func_oid)) + { delete_clause_func_oid = get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); - merge_clause_func_oid = - get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - if (strncmp(function_name, "_cypher_", sizeof("_cypher_") - 1) == 0) + return delete_clause_func_oid; +} + +static Oid get_merge_clause_func_oid(void) +{ + initialize_clause_function_oid_cache(); + + if (!OidIsValid(merge_clause_func_oid)) { - switch (function_name[sizeof("_cypher_") - 1]) - { - case 'c': - if (strcmp(function_name, CREATE_CLAUSE_FUNCTION_NAME) == 0) - { - return create_clause_func_oid; - } - break; - case 'd': - if (strcmp(function_name, DELETE_CLAUSE_FUNCTION_NAME) == 0) - { - return delete_clause_func_oid; - } - break; - case 'm': - if (strcmp(function_name, MERGE_CLAUSE_FUNCTION_NAME) == 0) - { - return merge_clause_func_oid; - } - break; - case 's': - if (strcmp(function_name, SET_CLAUSE_FUNCTION_NAME) == 0) - { - return set_clause_func_oid; - } - break; - } + merge_clause_func_oid = + get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - return get_ag_func_oid(function_name, 1, INTERNALOID); + return merge_clause_func_oid; } static Oid get_bool_or_func_oid(void) diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index fae9acedc..63f61c4a1 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -185,11 +185,14 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, int graph_name_len); static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); -static void load_vertex_hashtable(GRAPH_global_context *ggctx); -static void load_edge_hashtable(GRAPH_global_context *ggctx); +static void load_vertex_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, List *vertex_labels); +static void load_edge_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, List *edge_labels); static void freeze_GRAPH_global_hashtables(GRAPH_global_context *ggctx); -static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, - char label_type); +static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, + List **vertex_labels, List **edge_labels); +static void free_ag_label_names(List *labels); static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, graphid end_vertex_id, Oid edge_label_table_oid, @@ -206,6 +209,8 @@ static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, HTAB *relation_cache, const char *stale_msg); static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache); +static bool is_ggctx_invalid_with_snapshot(GRAPH_global_context *ggctx, + Snapshot *snapshot); /* definitions */ /* @@ -220,6 +225,14 @@ static Relation get_entry_property_relation(Oid relid, HTAB *relation_cache); * invalidation from unrelated transactions on the server. */ bool is_ggctx_invalid(GRAPH_global_context *ggctx) +{ + Snapshot snapshot = NULL; + + return is_ggctx_invalid_with_snapshot(ggctx, &snapshot); +} + +static bool is_ggctx_invalid_with_snapshot(GRAPH_global_context *ggctx, + Snapshot *snapshot) { /* use version counter if DSM or SHMEM mode is active */ if (version_mode == VERSION_MODE_DSM || version_mode == VERSION_MODE_SHMEM) @@ -244,13 +257,14 @@ bool is_ggctx_invalid(GRAPH_global_context *ggctx) } /* SNAPSHOT fallback: original behavior — check snapshot ids */ + if (*snapshot == NULL) { - Snapshot snap = GetActiveSnapshot(); - - return (ggctx->xmin != snap->xmin || - ggctx->xmax != snap->xmax || - ggctx->curcid != snap->curcid); + *snapshot = GetActiveSnapshot(); } + + return (ggctx->xmin != (*snapshot)->xmin || + ggctx->xmax != (*snapshot)->xmax || + ggctx->curcid != (*snapshot)->curcid); } /* * Helper function to create the global vertex and edge hashtables. One @@ -291,11 +305,10 @@ static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx) pfree_if_not_null(ehn); } -/* helper function to get a List of all label names for the specified graph */ -static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, - char label_type) +/* helper function to get label names for the specified graph */ +static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, + List **vertex_labels, List **edge_labels) { - List *labels = NIL; ScanKeyData scan_key; Relation ag_label; SysScanDesc scan_desc; @@ -324,16 +337,23 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, graph_label_entry *label; bool is_null; Datum datum; + char label_type; datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); - if (is_null || DatumGetChar(datum) != label_type) + if (is_null) { continue; } - datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); - if (!is_null) + label_type = DatumGetChar(datum); + if (label_type == LABEL_TYPE_VERTEX || label_type == LABEL_TYPE_EDGE) { + datum = heap_getattr(tuple, Anum_ag_label_name, tupdesc, &is_null); + if (is_null) + { + continue; + } + label = palloc(sizeof(*label)); namestrcpy(&label->name, NameStr(*DatumGetName(datum))); @@ -342,14 +362,31 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid graph_oid, Assert(!is_null); label->relation = DatumGetObjectId(datum); - labels = lappend(labels, label); + if (label_type == LABEL_TYPE_VERTEX) + { + *vertex_labels = lappend(*vertex_labels, label); + } + else + { + *edge_labels = lappend(*edge_labels, label); + } } } systable_endscan(scan_desc); table_close(ag_label, AccessShareLock); +} - return labels; +static void free_ag_label_names(List *labels) +{ + ListCell *lc; + + foreach(lc, labels) + { + pfree(lfirst(lc)); + } + + list_free(labels); } /* @@ -568,19 +605,11 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, } /* helper routine to load all vertices into the GRAPH global vertex hashtable */ -static void load_vertex_hashtable(GRAPH_global_context *ggctx) +static void load_vertex_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, List *vertex_labels) { - Oid graph_oid; - Snapshot snapshot; - List *vertex_labels = NIL; ListCell *lc; - /* get the specific graph OID and namespace (schema) OID */ - graph_oid = ggctx->graph_oid; - /* get the active snapshot */ - snapshot = GetActiveSnapshot(); - /* get metadata for all vertex label tables */ - vertex_labels = get_ag_labels_names(snapshot, graph_oid, LABEL_TYPE_VERTEX); /* go through all vertex label tables in list */ foreach (lc, vertex_labels) { @@ -660,34 +689,36 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx) */ static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx) { + Snapshot snapshot = GetActiveSnapshot(); + List *vertex_labels = NIL; + List *edge_labels = NIL; + /* initialize statistics */ ggctx->num_loaded_vertices = 0; ggctx->num_loaded_edges = 0; + get_ag_labels_names(snapshot, ggctx->graph_oid, &vertex_labels, + &edge_labels); + /* insert all of our vertices */ - load_vertex_hashtable(ggctx); + load_vertex_hashtable(ggctx, snapshot, vertex_labels); /* insert all of our edges */ - load_edge_hashtable(ggctx); + load_edge_hashtable(ggctx, snapshot, edge_labels); + + free_ag_label_names(vertex_labels); + free_ag_label_names(edge_labels); } /* * Helper routine to load all edges into the GRAPH global edge and vertex * hashtables. */ -static void load_edge_hashtable(GRAPH_global_context *ggctx) +static void load_edge_hashtable(GRAPH_global_context *ggctx, + Snapshot snapshot, List *edge_labels) { - Oid graph_oid; - Snapshot snapshot; - List *edge_labels = NIL; ListCell *lc; - /* get the specific graph OID and namespace (schema) OID */ - graph_oid = ggctx->graph_oid; - /* get the active snapshot */ - snapshot = GetActiveSnapshot(); - /* get metadata for all edge label tables */ - edge_labels = get_ag_labels_names(snapshot, graph_oid, LABEL_TYPE_EDGE); /* go through all edge label tables in list */ foreach (lc, edge_labels) { @@ -913,6 +944,8 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( GRAPH_global_context *new_ggctx = NULL; GRAPH_global_context *curr_ggctx = NULL; GRAPH_global_context *prev_ggctx = NULL; + GRAPH_global_context *found_ggctx = NULL; + Snapshot invalidation_snapshot = NULL; MemoryContext oldctx = NULL; /* we need a higher context, or one that isn't destroyed by SRF exit */ @@ -940,7 +973,8 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( GRAPH_global_context *next_ggctx = curr_ggctx->next; /* if the transaction ids have changed, we have an invalid graph */ - if (is_ggctx_invalid(curr_ggctx)) + if (is_ggctx_invalid_with_snapshot(curr_ggctx, + &invalidation_snapshot)) { bool success = false; @@ -973,6 +1007,10 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( } else { + if (found_ggctx == NULL && curr_ggctx->graph_oid == graph_oid) + { + found_ggctx = curr_ggctx; + } prev_ggctx = curr_ggctx; } @@ -980,21 +1018,15 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( curr_ggctx = next_ggctx; } - /* find our graph's context. if it exists, we are done */ - curr_ggctx = global_graph_contexts_container.contexts; - while (curr_ggctx != NULL) + if (found_ggctx != NULL) { - if (curr_ggctx->graph_oid == graph_oid) - { - /* switch our context back */ - MemoryContextSwitchTo(oldctx); + /* switch our context back */ + MemoryContextSwitchTo(oldctx); - /* we are done unlock the global contexts list */ - pthread_mutex_unlock(&global_graph_contexts_container.mutex_lock); + /* we are done unlock the global contexts list */ + pthread_mutex_unlock(&global_graph_contexts_container.mutex_lock); - return curr_ggctx; - } - curr_ggctx = curr_ggctx->next; + return found_ggctx; } /* otherwise, we need to create one and possibly attach it */ diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index c151608c7..140539fbd 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -33,8 +33,6 @@ #include "utils/jsonfuncs.h" #include -#include - #include "access/genam.h" #include "access/heapam.h" #include "catalog/namespace.h" @@ -2527,20 +2525,22 @@ Datum make_path(List *path) agtype *agt_result; ListCell *lc; agtype_in_state result; + int path_len; int i = 1; memset(&result, 0, sizeof(agtype_in_state)); result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, NULL); + path_len = list_length(path); - if (list_length(path) < 1) + if (path_len < 1) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("paths require at least 1 vertex"))); } - if (list_length(path) % 2 != 1) + if (path_len % 2 != 1) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2551,8 +2551,6 @@ Datum make_path(List *path) foreach (lc, path) { agtype *agt = DATUM_GET_AGTYPE_P(PointerGetDatum(lfirst(lc))); - agtype_value *elem; - elem = get_ith_agtype_value_from_container(&agt->root, 0); if (!agt) { @@ -2560,13 +2558,15 @@ Datum make_path(List *path) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument must not be null"))); } - else if (i % 2 == 1 && elem->type != AGTV_VERTEX) + else if (i % 2 == 1 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || + agt->root.children[1] != AGT_HEADER_VERTEX)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("argument %i must be a vertex", i))); } - else if (i % 2 == 0 && elem->type != AGTV_EDGE) + else if (i % 2 == 0 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || + agt->root.children[1] != AGT_HEADER_EDGE)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -2579,7 +2579,6 @@ Datum make_path(List *path) { pfree_if_not_null(agt); } - pfree_agtype_value(elem); i++; } @@ -7919,8 +7918,8 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { int len; - len = sprintf(buffer, "%.*g", DBL_DIG, elem->val.float_value); - string_elem.val.string.val = pnstrdup(buffer, len); + string_elem.val.string.val = float8out_internal(elem->val.float_value); + len = strlen(string_elem.val.string.val); string_elem.val.string.len = len; agis_result.res = push_agtype_value(&agis_result.parse_state, @@ -7933,7 +7932,7 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { int len; - len = sprintf(buffer, "%ld", elem->val.int_value); + len = pg_lltoa(elem->val.int_value, buffer); string_elem.val.string.val = pnstrdup(buffer, len); string_elem.val.string.len = len; @@ -9144,7 +9143,6 @@ Datum age_split(PG_FUNCTION_ARGS) char *string; int string_len; agtype_value agtv_string; - Datum d; /* get the string element from the array */ string = VARDATA_ANY(elem); @@ -9155,11 +9153,8 @@ Datum age_split(PG_FUNCTION_ARGS) agtv_string.val.string.val = string; agtv_string.val.string.len = string_len; - /* get the datum */ - d = PointerGetDatum(agtype_value_to_agtype(&agtv_string)); - - /* add the value */ - add_agtype(d, false, &result, AGTYPEOID, false); + result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, + &agtv_string); } /* close the array */ diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index b44a43fcc..ea3f9e58e 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -38,6 +38,8 @@ static agtype_value *iterator_concat(agtype_iterator **it1, static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen); static char *get_string_from_agtype_value(agtype_value *agtv, int *length); +static text *agtype_container_to_text(agtype_container *container, + int estimated_len); static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text); static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen); static agtype *delete_from_array(agtype *agt, agtype* indexes); @@ -106,6 +108,17 @@ static char *get_string_from_agtype_value(agtype_value *agtv, int *length) return NULL; } +static text *agtype_container_to_text(agtype_container *container, + int estimated_len) +{ + StringInfoData out; + + initStringInfo(&out); + agtype_to_cstring(&out, container, estimated_len); + + return cstring_to_text_with_len(out.data, out.len); +} + static bool parse_agtype_index_string(char *str, int len, long *lindex) { int i = 0; @@ -2139,8 +2152,7 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) { if (as_text) { - PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, container, - VARSIZE(agt)))); + PG_RETURN_TEXT_P(agtype_container_to_text(container, VARSIZE(agt))); } else { @@ -2277,9 +2289,7 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) if (as_text) { - PG_RETURN_TEXT_P(cstring_to_text(agtype_to_cstring(NULL, - &res->root, - VARSIZE(res)))); + PG_RETURN_TEXT_P(agtype_container_to_text(&res->root, VARSIZE(res))); } else { diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index edf08c860..0ca66796a 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -59,6 +59,7 @@ typedef struct cypher_create_custom_scan_state Oid graph_oid; HTAB *entity_exists_index_cache; HTAB *result_rel_info_cache; + bool graph_mutated; } cypher_create_custom_scan_state; typedef struct cypher_set_custom_scan_state @@ -72,6 +73,9 @@ typedef struct cypher_set_custom_scan_state HTAB *result_rel_info_cache; Oid graph_oid; int flags; + int *path_update_attrs; + int path_update_attr_count; + bool path_update_attrs_valid; } cypher_set_custom_scan_state; typedef struct cypher_delete_custom_scan_state @@ -102,6 +106,7 @@ typedef struct cypher_delete_custom_scan_state HTAB *qual_cache; HTAB *index_cache; HTAB *result_rel_info_cache; + bool graph_mutated; } cypher_delete_custom_scan_state; typedef struct cypher_merge_custom_scan_state @@ -115,9 +120,11 @@ typedef struct cypher_merge_custom_scan_state Oid graph_oid; AttrNumber merge_function_attr; bool created_new_path; + bool graph_mutated; bool found_a_path; CommandId base_currentCommandId; struct created_path *created_paths_list; + HTAB *created_paths_hash; int path_length; List *eager_tuples; ListCell *eager_tuples_cursor; @@ -126,6 +133,9 @@ typedef struct cypher_merge_custom_scan_state cypher_update_information *on_create_set_info; /* NULL if not specified */ int on_match_set_item_count; int on_create_set_item_count; + int *path_update_attrs; + int path_update_attr_count; + bool path_update_attrs_valid; HTAB *update_qual_cache; HTAB *update_index_cache; HTAB *update_result_rel_info_cache; @@ -134,7 +144,7 @@ typedef struct cypher_merge_custom_scan_state } cypher_merge_custom_scan_state; /* Reusable SET logic callable from MERGE executor */ -void apply_update_list(CustomScanState *node, +bool apply_update_list(CustomScanState *node, cypher_update_information *set_info, int num_set_items); diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 4abab5459..80d6c81e1 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -463,6 +463,9 @@ typedef struct cypher_update_information char *graph_name; uint32 graph_oid; char *clause_name; + int *last_update_indexes; + int last_update_index_count; + bool last_update_indexes_valid; } cypher_update_information; typedef struct cypher_update_item @@ -488,6 +491,9 @@ typedef struct cypher_delete_information char *graph_name; uint32 graph_oid; bool detach; + int *delete_item_positions; + int delete_item_count; + bool delete_item_positions_valid; } cypher_delete_information; typedef struct cypher_delete_item From cb325070dce3b8eeafd2da7d0f1af027901efb81 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:38 +0900 Subject: [PATCH 11/15] Avoid agtype scalar wrapper copies Reduce unnecessary agtype value, entity, path, key, and wrapper copies across scalar operators, casts, string helpers, list functions, VLE quals, endpoint lookups, and DML propagation. The change releases temporary graph values as soon as field, label, id, or path extraction is complete and keeps no-copy helper usage together so related memory-lifetime assumptions can be reviewed in one place. --- src/backend/commands/label_commands.c | 17 +- src/backend/executor/cypher_create.c | 21 +- src/backend/executor/cypher_delete.c | 77 +- src/backend/executor/cypher_merge.c | 43 +- src/backend/executor/cypher_set.c | 91 +- src/backend/utils/adt/age_global_graph.c | 146 +- src/backend/utils/adt/age_vle.c | 397 ++++-- src/backend/utils/adt/agtype.c | 1639 +++++++++++++--------- src/backend/utils/adt/agtype_ops.c | 220 ++- src/backend/utils/adt/agtype_util.c | 37 + src/include/utils/agtype.h | 4 + 11 files changed, 1800 insertions(+), 892 deletions(-) diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index fe937d242..70f640e4d 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -94,9 +94,10 @@ PG_FUNCTION_INFO_V1(age_is_valid_label_name); Datum age_is_valid_label_name(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; + agtype_value agtv_value; char *label_name = NULL; bool is_valid = false; + bool value_needs_free = false; if (PG_ARGISNULL(0)) { @@ -113,17 +114,23 @@ Datum age_is_valid_label_name(PG_FUNCTION_ARGS) errmsg("is_valid_label_name() only supports scalar arguments"))); } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); - if (agtv_value->type != AGTV_STRING) + if (agtv_value.type != AGTV_STRING) { + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("is_valid_label_name() only supports string arguments"))); } - label_name = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + label_name = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); is_valid = is_valid_label_name(label_name, 0); pfree_if_not_null(label_name); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 5fd2f5256..bd93bb1c0 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -552,8 +552,10 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, else { agtype *a; - agtype_value *v; + agtype_value v; agtype_value *id_value; + bool v_needs_free = false; + bool v_found; TupleTableSlot *scantuple; PlanState *ps; @@ -564,21 +566,32 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, a = DATUM_GET_AGTYPE_P(scantuple->tts_values[node->tuple_position - 1]); /* Convert to an agtype value */ - v = get_ith_agtype_value_from_container(&a->root, 0); + v_found = get_ith_agtype_value_from_container_no_copy(&a->root, 0, &v, + &v_needs_free); + Assert(v_found); - if (v->type != AGTV_VERTEX) + if (v.type != AGTV_VERTEX) { + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - id_value = AGTYPE_VERTEX_GET_ID(v); + id_value = AGTYPE_VERTEX_GET_ID(&v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } + /* * Its possible the variable has already been deleted. There are two * ways this can happen. One is the query explicitly deleted the diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 552a4384a..8065afe6b 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -46,9 +46,11 @@ static void ensure_detach_delete_rls(CustomScanState *node, Oid relid, bool *rls_checked, bool *rls_enabled, List **qualExprs, ExprContext **econtext); -static agtype_value *extract_entity(CustomScanState *node, - TupleTableSlot *scanTupleSlot, - int entity_position); +static void extract_entity(CustomScanState *node, + TupleTableSlot *scanTupleSlot, + int entity_position, + enum agtype_value_type *entity_type, + graphid *entity_id); static bool delete_entity(EState *estate, ResultRelInfo *resultRelInfo, HeapTuple tuple); static void init_delete_caches(cypher_delete_custom_scan_state *css); @@ -355,13 +357,17 @@ Node *create_cypher_delete_plan_state(CustomScan *cscan) * Extract the vertex or edge to be deleted, perform some type checking to * validate datum is an agtype vertex or edge. */ -static agtype_value *extract_entity(CustomScanState *node, - TupleTableSlot *scanTupleSlot, - int entity_position) +static void extract_entity(CustomScanState *node, + TupleTableSlot *scanTupleSlot, + int entity_position, + enum agtype_value_type *entity_type, + graphid *entity_id) { - agtype_value *original_entity_value; + agtype_value original_entity_value; + agtype_value *id; agtype *original_entity; TupleDesc tupleDescriptor; + bool entity_needs_free = false; tupleDescriptor = scanTupleSlot->tts_tupleDescriptor; @@ -370,14 +376,32 @@ static agtype_value *extract_entity(CustomScanState *node, ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DELETE clause can only delete agtype"))); - original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[entity_position - 1]); - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + original_entity = + DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[entity_position - 1]); + (void)get_ith_agtype_value_from_container_no_copy(&original_entity->root, + 0, + &original_entity_value, + &entity_needs_free); - if (original_entity_value->type != AGTV_VERTEX && original_entity_value->type != AGTV_EDGE) + if (original_entity_value.type != AGTV_VERTEX && + original_entity_value.type != AGTV_EDGE) + { + if (entity_needs_free) + pfree_agtype_value_content(&original_entity_value); ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("DELETE clause can only delete vertices and edges"))); + } + + if (original_entity_value.type == AGTV_VERTEX) + id = AGTYPE_VERTEX_GET_ID(&original_entity_value); + else + id = AGTYPE_EDGE_GET_ID(&original_entity_value); + + *entity_type = original_entity_value.type; + *entity_id = id->val.int_value; - return original_entity_value; + if (entity_needs_free) + pfree_agtype_value_content(&original_entity_value); } /* @@ -495,7 +519,8 @@ static void process_delete_list(CustomScanState *node) for (i = 0; i < delete_item_count; i++) { - agtype_value *original_entity_value, *id; + enum agtype_value_type entity_type; + graphid entity_id; ScanKeyData scan_keys[1]; TableScanDesc scan_desc = NULL; ResultRelInfo *resultRelInfo; @@ -521,25 +546,17 @@ static void process_delete_list(CustomScanState *node) if (scanTupleSlot->tts_isnull[entity_position - 1]) continue; - original_entity_value = extract_entity(node, scanTupleSlot, - entity_position); + extract_entity(node, scanTupleSlot, entity_position, &entity_type, + &entity_id); - if (original_entity_value->type == AGTV_VERTEX) - { - id = AGTYPE_VERTEX_GET_ID(original_entity_value); - } - else - { - id = AGTYPE_EDGE_GET_ID(original_entity_value); - } label_cache = search_label_graph_oid_cache_cached( - css->delete_data->graph_oid, GET_LABEL_ID(id->val.int_value)); + css->delete_data->graph_oid, GET_LABEL_ID(entity_id)); if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("label id %lu does not exist", - GET_LABEL_ID(id->val.int_value)))); + GET_LABEL_ID(entity_id)))); } resultRelInfo = get_entity_result_rel_info(estate, @@ -552,19 +569,19 @@ static void process_delete_list(CustomScanState *node) * Setup the scan key to require the id field on-disc to match the * entity's graphid. */ - if (original_entity_value->type == AGTV_VERTEX) + if (entity_type == AGTV_VERTEX) { id_attr_num = Anum_ag_label_vertex_table_id; ScanKeyInit(&scan_keys[0], Anum_ag_label_vertex_table_id, BTEqualStrategyNumber, F_GRAPHIDEQ, - GRAPHID_GET_DATUM(id->val.int_value)); + GRAPHID_GET_DATUM(entity_id)); } - else if (original_entity_value->type == AGTV_EDGE) + else if (entity_type == AGTV_EDGE) { id_attr_num = Anum_ag_label_edge_table_id; ScanKeyInit(&scan_keys[0], Anum_ag_label_edge_table_id, BTEqualStrategyNumber, F_GRAPHIDEQ, - GRAPHID_GET_DATUM(id->val.int_value)); + GRAPHID_GET_DATUM(entity_id)); } else { @@ -669,7 +686,7 @@ static void process_delete_list(CustomScanState *node) * For vertices, store successfully deleted IDs for the * later connected-edge pass. */ - if (original_entity_value->type == AGTV_VERTEX) + if (entity_type == AGTV_VERTEX) { bool found; @@ -679,7 +696,7 @@ static void process_delete_list(CustomScanState *node) } hash_search(css->vertex_id_htab, - (void *)&(id->val.int_value), + (void *)&entity_id, HASH_ENTER, &found); } } diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 1bd150551..1cc12d54f 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -546,8 +546,10 @@ static path_entry **prebuild_path(CustomScanState *node) agtype *agt = NULL; Datum d; - agtype_value *agtv_vertex = NULL; + agtype_value agtv_vertex; agtype_value *agtv_id = NULL; + bool vertex_needs_free = false; + bool vertex_found; /* check that the variable isn't NULL */ if (scanTupleSlot->tts_isnull[node->tuple_position - 1]) @@ -563,21 +565,33 @@ static path_entry **prebuild_path(CustomScanState *node) agt = DATUM_GET_AGTYPE_P(d); /* Convert to an agtype value */ - agtv_vertex = get_ith_agtype_value_from_container(&agt->root, 0); + vertex_found = get_ith_agtype_value_from_container_no_copy( + &agt->root, 0, &agtv_vertex, &vertex_needs_free); + Assert(vertex_found); - if (agtv_vertex->type != AGTV_VERTEX) + if (agtv_vertex.type != AGTV_VERTEX) { + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - agtv_id = AGTYPE_VERTEX_GET_ID(agtv_vertex); + agtv_id = AGTYPE_VERTEX_GET_ID(&agtv_vertex); /* set the necessary entry fields - actual & id */ entry->actual = true; entry->id = (graphid) agtv_id->val.int_value; + + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } + entry->id_isNull = false; entry->prop = 0; entry->prop_isNull = true; @@ -1570,8 +1584,10 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, { agtype *a = NULL; Datum d; - agtype_value *v = NULL; + agtype_value v; agtype_value *id_value = NULL; + bool v_needs_free = false; + bool v_found; /* check that the variable isn't NULL */ if (scanTupleSlot->tts_isnull[node->tuple_position - 1]) @@ -1587,21 +1603,32 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, a = DATUM_GET_AGTYPE_P(d); /* Convert to an agtype value */ - v = get_ith_agtype_value_from_container(&a->root, 0); + v_found = get_ith_agtype_value_from_container_no_copy(&a->root, 0, &v, + &v_needs_free); + Assert(v_found); - if (v->type != AGTV_VERTEX) + if (v.type != AGTV_VERTEX) { + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("agtype must resolve to a vertex"))); } /* extract the id agtype field */ - id_value = AGTYPE_VERTEX_GET_ID(v); + id_value = AGTYPE_VERTEX_GET_ID(&v); /* extract the graphid and cast to a Datum */ id = GRAPHID_GET_DATUM(id_value->val.int_value); + if (v_needs_free) + { + pfree_agtype_value_content(&v); + } + /* * Its possible the variable has already been deleted. There are two * ways this can happen. One is the query explicitly deleted the diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 0f4f904ce..84233e8ff 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -353,13 +353,16 @@ static bool check_path(agtype_value *path, graphid updated_id) * Construct a new agtype path with the entity with updated_id * replacing all of its instances in path with updated_entity */ -static agtype_value *replace_entity_in_path(agtype_value *path, - graphid updated_id, - agtype *updated_entity) +static agtype *replace_entity_in_path(agtype_value *path, + graphid updated_id, + agtype *updated_entity) { agtype_parse_state *parse_state = NULL; agtype_value *parsed_agtype_value = NULL; - agtype_value *updated_entity_value = NULL; + agtype_value updated_entity_value; + agtype *result; + bool updated_entity_value_valid = false; + bool updated_entity_needs_free = false; int i; parsed_agtype_value = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, @@ -395,15 +398,16 @@ static agtype_value *replace_entity_in_path(agtype_value *path, */ if (updated_id == id->val.int_value) { - if (updated_entity_value == NULL) + if (!updated_entity_value_valid) { - updated_entity_value = - get_ith_agtype_value_from_container(&updated_entity->root, - 0); + (void)get_ith_agtype_value_from_container_no_copy( + &updated_entity->root, 0, &updated_entity_value, + &updated_entity_needs_free); + updated_entity_value_valid = true; } parsed_agtype_value = push_agtype_value(&parse_state, WAGT_ELEM, - updated_entity_value); + &updated_entity_value); } else { @@ -415,7 +419,13 @@ static agtype_value *replace_entity_in_path(agtype_value *path, parsed_agtype_value = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); parsed_agtype_value->type = AGTV_PATH; - return parsed_agtype_value; + result = agtype_value_to_agtype(parsed_agtype_value); + if (updated_entity_needs_free) + { + pfree_agtype_value_content(&updated_entity_value); + } + + return result; } /* @@ -444,7 +454,8 @@ static void update_all_paths(CustomScanState *node, graphid id, for (i = 0; i < attr_count; i++) { agtype *original_entity; - agtype_value *original_entity_value; + agtype_value original_entity_value; + bool entity_needs_free = false; int attr = attrs[i]; if (isnull[attr]) @@ -460,20 +471,28 @@ static void update_all_paths(CustomScanState *node, graphid id, continue; } - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &original_entity->root, 0, &original_entity_value, + &entity_needs_free); /* we found a path */ - if (original_entity_value->type == AGTV_PATH) + if (original_entity_value.type == AGTV_PATH) { /* check if the path contains the entity. */ - if (check_path(original_entity_value, id)) + if (check_path(&original_entity_value, id)) { /* the path does contain the entity replace with the new entity. */ - agtype_value *new_path = replace_entity_in_path(original_entity_value, id, updated_entity); + agtype *new_path = replace_entity_in_path( + &original_entity_value, id, updated_entity); - values[attr] = AGTYPE_P_GET_DATUM(agtype_value_to_agtype(new_path)); + values[attr] = AGTYPE_P_GET_DATUM(new_path); } } + + if (entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } } } @@ -635,7 +654,7 @@ bool apply_update_list(CustomScanState *node, foreach (lc, set_info->set_items) { agtype_value *altered_properties; - agtype_value *original_entity_value; + agtype_value original_entity_value; agtype_value *original_properties; agtype_value *id; label_cache_data *label_cache; @@ -660,6 +679,7 @@ bool apply_update_list(CustomScanState *node, bool found_idx_entry; RLSCacheEntry *rls_entry = NULL; bool found_rls_entry; + bool original_entity_needs_free = false; update_item = (cypher_update_item *)lfirst(lc); @@ -681,11 +701,17 @@ bool apply_update_list(CustomScanState *node, } original_entity = DATUM_GET_AGTYPE_P(scanTupleSlot->tts_values[update_item->entity_position - 1]); - original_entity_value = get_ith_agtype_value_from_container(&original_entity->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &original_entity->root, 0, &original_entity_value, + &original_entity_needs_free); - if (original_entity_value->type != AGTV_VERTEX && - original_entity_value->type != AGTV_EDGE) + if (original_entity_value.type != AGTV_VERTEX && + original_entity_value.type != AGTV_EDGE) { + if (original_entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("age %s clause can only update vertex and edges", @@ -693,17 +719,17 @@ bool apply_update_list(CustomScanState *node, } /* get the id and label metadata for later */ - if (original_entity_value->type == AGTV_VERTEX) + if (original_entity_value.type == AGTV_VERTEX) { - id = AGTYPE_VERTEX_GET_ID(original_entity_value); + id = AGTYPE_VERTEX_GET_ID(&original_entity_value); original_properties = - AGTYPE_VERTEX_GET_PROPERTIES(original_entity_value); + AGTYPE_VERTEX_GET_PROPERTIES(&original_entity_value); } else { - id = AGTYPE_EDGE_GET_ID(original_entity_value); + id = AGTYPE_EDGE_GET_ID(&original_entity_value); original_properties = - AGTYPE_EDGE_GET_PROPERTIES(original_entity_value); + AGTYPE_EDGE_GET_PROPERTIES(&original_entity_value); } label_cache = search_label_graph_oid_cache_cached( graph_oid, GET_LABEL_ID(id->val.int_value)); @@ -812,16 +838,16 @@ bool apply_update_list(CustomScanState *node, * metadata for the on-disc update are only needed for the last update * targeting this entity. */ - if (original_entity_value->type == AGTV_VERTEX) + if (original_entity_value.type == AGTV_VERTEX) { new_entity = make_vertex(GRAPHID_GET_DATUM(id->val.int_value), CStringGetDatum(label_name), AGTYPE_P_GET_DATUM(agtype_value_to_agtype(altered_properties))); } - else if (original_entity_value->type == AGTV_EDGE) + else if (original_entity_value.type == AGTV_EDGE) { - startid = AGTYPE_EDGE_GET_START_ID(original_entity_value); - endid = AGTYPE_EDGE_GET_END_ID(original_entity_value); + startid = AGTYPE_EDGE_GET_START_ID(&original_entity_value); + endid = AGTYPE_EDGE_GET_END_ID(&original_entity_value); new_entity = make_edge(GRAPHID_GET_DATUM(id->val.int_value), GRAPHID_GET_DATUM(startid->val.int_value), @@ -926,7 +952,7 @@ bool apply_update_list(CustomScanState *node, ExecClearTuple(slot); } - if (original_entity_value->type == AGTV_VERTEX) + if (original_entity_value.type == AGTV_VERTEX) { slot = populate_vertex_tts(slot, id, altered_properties); } @@ -1055,6 +1081,11 @@ bool apply_update_list(CustomScanState *node, estate->es_snapshot->curcid = cid; } + if (original_entity_needs_free) + { + pfree_agtype_value_content(&original_entity_value); + } + /* increment loop index */ lidx++; } diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index 63f61c4a1..d2b3d7448 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -183,6 +183,12 @@ static bool delete_specific_GRAPH_global_contexts_len(const char *graph_name, static bool delete_GRAPH_global_contexts(void); static Oid get_cached_global_graph_oid_len(const char *graph_name, int graph_name_len); +static void get_global_graph_scalar_arg_no_copy(char *funcname, + agtype *agt_arg, + enum agtype_value_type type, + bool error, + agtype_value *result, + bool *needs_free); static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_vertex_hashtable(GRAPH_global_context *ggctx, @@ -1200,6 +1206,55 @@ static Oid get_cached_global_graph_oid_len(const char *graph_name, return cached_graph_oid; } +static void get_global_graph_scalar_arg_no_copy(char *funcname, + agtype *agt_arg, + enum agtype_value_type type, + bool error, + agtype_value *result, + bool *needs_free) +{ + bool found; + + Assert(funcname != NULL); + Assert(agt_arg != NULL); + Assert(result != NULL); + Assert(needs_free != NULL); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must be a scalar", funcname))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + result, needs_free); + Assert(found); + + if (error && result->type == AGTV_NULL) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must not be AGTV_NULL", + funcname))); + } + + if (error && result->type != type) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument of wrong type", funcname))); + } +} + static bool delete_specific_GRAPH_global_context_by_oid(Oid graph_oid) { GRAPH_global_context *prev_ggctx = NULL; @@ -1558,25 +1613,30 @@ PG_FUNCTION_INFO_V1(age_delete_global_graphs); Datum age_delete_global_graphs(PG_FUNCTION_ARGS) { - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + agtype_value *agtv_temp_ptr = NULL; + bool agtv_temp_needs_free = false; bool success = false; /* get the graph name if supplied */ if (!PG_ARGISNULL(0)) { - agtv_temp = get_agtype_value("delete_global_graphs", - AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, false); + get_global_graph_scalar_arg_no_copy("delete_global_graphs", + AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, false, + &agtv_temp, + &agtv_temp_needs_free); + agtv_temp_ptr = &agtv_temp; } - if (agtv_temp == NULL || agtv_temp->type == AGTV_NULL) + if (agtv_temp_ptr == NULL || agtv_temp_ptr->type == AGTV_NULL) { success = delete_GRAPH_global_contexts(); } - else if (agtv_temp->type == AGTV_STRING) + else if (agtv_temp_ptr->type == AGTV_STRING) { success = delete_specific_GRAPH_global_contexts_len( - agtv_temp->val.string.val, agtv_temp->val.string.len); + agtv_temp_ptr->val.string.val, agtv_temp_ptr->val.string.len); } else { @@ -1585,6 +1645,11 @@ Datum age_delete_global_graphs(PG_FUNCTION_ARGS) errmsg("delete_global_graphs: invalid graph name type"))); } + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + } + PG_RETURN_BOOL(success); } @@ -1596,10 +1661,14 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) GRAPH_global_context *ggctx = NULL; vertex_entry *ve = NULL; ListGraphId *edges = NULL; - agtype_value *agtv_vertex = NULL; + agtype_value agtv_vertex; agtype_value *agtv_temp = NULL; + agtype_value graph_name_value; + bool graph_name_needs_free = false; + bool vertex_needs_free = false; agtype_value agtv_integer; agtype_in_state result; + agtype *agt_result; Oid graph_oid = InvalidOid; graphid vid = 0; int64 self_loops = 0; @@ -1614,8 +1683,9 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) } /* get the graph name */ - agtv_temp = get_agtype_value("vertex_stats", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); + get_global_graph_scalar_arg_no_copy("vertex_stats", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &graph_name_value, + &graph_name_needs_free); /* we need the vertex */ if (PG_ARGISNULL(1)) @@ -1626,23 +1696,24 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) } /* get the vertex */ - agtv_vertex = get_agtype_value("vertex_stats", AG_GET_ARG_AGTYPE_P(1), - AGTV_VERTEX, true); + get_global_graph_scalar_arg_no_copy("vertex_stats", AG_GET_ARG_AGTYPE_P(1), + AGTV_VERTEX, true, &agtv_vertex, + &vertex_needs_free); /* get the graph oid */ - graph_oid = get_cached_global_graph_oid_len(agtv_temp->val.string.val, - agtv_temp->val.string.len); + graph_oid = get_cached_global_graph_oid_len(graph_name_value.val.string.val, + graph_name_value.val.string.len); /* * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, - agtv_temp->val.string.len, + ggctx = manage_GRAPH_global_contexts_len(graph_name_value.val.string.val, + graph_name_value.val.string.len, graph_oid); /* get the id */ - agtv_temp = AGTYPE_VERTEX_GET_ID(agtv_vertex); + agtv_temp = AGTYPE_VERTEX_GET_ID(&agtv_vertex); vid = agtv_temp->val.int_value; /* get the vertex entry */ @@ -1660,7 +1731,7 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); /* store the label */ - agtv_temp = AGTYPE_VERTEX_GET_LABEL(agtv_vertex); + agtv_temp = AGTYPE_VERTEX_GET_LABEL(&agtv_vertex); result.res = push_agtype_value(&result.parse_state, WAGT_KEY, string_to_agtype_value("label")); result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); @@ -1699,7 +1770,17 @@ Datum age_vertex_stats(PG_FUNCTION_ARGS) result.res->type = AGTV_OBJECT; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (graph_name_needs_free) + { + pfree_agtype_value_content(&graph_name_value); + } + if (vertex_needs_free) + { + pfree_agtype_value_content(&agtv_vertex); + } + + PG_RETURN_POINTER(agt_result); } /* PG wrapper function for age_graph_stats */ @@ -1709,8 +1790,11 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) { GRAPH_global_context *ggctx = NULL; agtype_value *agtv_temp = NULL; + agtype_value graph_name_value; + bool graph_name_needs_free = false; agtype_value agtv_integer; agtype_in_state result; + agtype *agt_result; Oid graph_oid = InvalidOid; /* the graph name is required, but this generally isn't user supplied */ @@ -1722,12 +1806,13 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) } /* get the graph name */ - agtv_temp = get_agtype_value("graph_stats", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); + get_global_graph_scalar_arg_no_copy("graph_stats", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &graph_name_value, + &graph_name_needs_free); /* get the graph oid */ - graph_oid = get_cached_global_graph_oid_len(agtv_temp->val.string.val, - agtv_temp->val.string.len); + graph_oid = get_cached_global_graph_oid_len(graph_name_value.val.string.val, + graph_name_value.val.string.len); /* * Remove any context for this graph. This is done to allow graph_stats to @@ -1739,8 +1824,8 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, - agtv_temp->val.string.len, + ggctx = manage_GRAPH_global_contexts_len(graph_name_value.val.string.val, + graph_name_value.val.string.len, graph_oid); /* zero the state */ @@ -1752,7 +1837,8 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) /* store the graph name */ result.res = push_agtype_value(&result.parse_state, WAGT_KEY, string_to_agtype_value("graph")); - result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, agtv_temp); + result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, + &graph_name_value); /* set up an integer for returning values */ agtv_temp = &agtv_integer; @@ -1776,7 +1862,13 @@ Datum age_graph_stats(PG_FUNCTION_ARGS) result.res->type = AGTV_OBJECT; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (graph_name_needs_free) + { + pfree_agtype_value_content(&graph_name_value); + } + + PG_RETURN_POINTER(agt_result); } /* diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index af54a8153..116140d3d 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -166,9 +166,17 @@ typedef struct edge_uniqueness_argtype_cache static VLE_local_context *global_vle_local_contexts = NULL; /* agtype functions */ -static agtype_value *get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, - int argno, - const char *type_error_msg); +static bool get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, int argno, + const char *type_error_msg, + graphid *result); +static void get_vle_scalar_arg_no_copy(char *funcname, agtype *agt_arg, + enum agtype_value_type type, bool error, + agtype_value *result, + bool *needs_free); +static graphid get_agtype_scalar_graphid_arg(agtype *agt_arg, + const char *type_error_msg); +static bool get_agtype_scalar_bool_arg(agtype *agt_arg, + const char *type_error_msg); static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, HTAB *relation_cache); /* VLE local context functions */ @@ -492,16 +500,18 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, } } -static agtype_value *get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, - int argno, - const char *type_error_msg) +static bool get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, int argno, + const char *type_error_msg, + graphid *result) { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + agtype_value *id; + bool value_needs_free = false; if (PG_ARGISNULL(argno)) { - return NULL; + return false; } agt_arg = AG_GET_ARG_AGTYPE_P(argno); @@ -513,24 +523,168 @@ static agtype_value *get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, errmsg("age_vle: agtype argument must be a scalar"))); } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - if (agtv_value->type == AGTV_NULL) + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + if (agtv_value.type == AGTV_NULL) { - return NULL; + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + return false; + } + + if (agtv_value.type == AGTV_VERTEX) + { + id = AGTYPE_VERTEX_GET_ID(&agtv_value); + } + else if (agtv_value.type == AGTV_INTEGER) + { + id = &agtv_value; + } + else + { + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + *result = id->val.int_value; + + if (value_needs_free) + pfree_agtype_value_content(&agtv_value); + + return true; +} + +static void get_vle_scalar_arg_no_copy(char *funcname, agtype *agt_arg, + enum agtype_value_type type, bool error, + agtype_value *result, + bool *needs_free) +{ + bool found; + + Assert(funcname != NULL); + Assert(agt_arg != NULL); + Assert(result != NULL); + Assert(needs_free != NULL); + + if (!AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must be a scalar", funcname))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + result, needs_free); + Assert(found); + + if (error && result->type == AGTV_NULL) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument must not be AGTV_NULL", + funcname))); + } + + if (error && result->type != type) + { + if (*needs_free) + { + pfree_agtype_value_content(result); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s: agtype argument of wrong type", funcname))); + } +} + +static graphid get_agtype_scalar_graphid_arg(agtype *agt_arg, + const char *type_error_msg) +{ + agtype_value agtv_value; + bool value_needs_free = false; + graphid result; + bool found; + + if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + Assert(found); + + if (agtv_value.type != AGTV_INTEGER) + { + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); } - if (agtv_value->type == AGTV_VERTEX) + result = agtv_value.val.int_value; + + if (value_needs_free) { - agtv_value = AGTYPE_VERTEX_GET_ID(agtv_value); + pfree_agtype_value_content(&agtv_value); } - else if (agtv_value->type != AGTV_INTEGER) + + return result; +} + +static bool get_agtype_scalar_bool_arg(agtype *agt_arg, + const char *type_error_msg) +{ + agtype_value agtv_value; + bool value_needs_free = false; + bool result; + bool found; + + if (!AGT_ROOT_IS_SCALAR(agt_arg)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s", type_error_msg))); + } + + found = get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &agtv_value, + &value_needs_free); + Assert(found); + + if (agtv_value.type != AGTV_BOOL) { + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s", type_error_msg))); } - return agtv_value; + result = agtv_value.val.boolean; + + if (value_needs_free) + { + pfree_agtype_value_content(&agtv_value); + } + + return result; } /* @@ -641,7 +795,8 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, MemoryContext oldctx = NULL; GRAPH_global_context *ggctx = NULL; VLE_local_context *vlelctx = NULL; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + bool agtv_temp_needs_free = false; agtype_value *agtv_object = NULL; agtype *agt_edge_property_constraint = NULL; agtype *agt_arg = NULL; @@ -650,6 +805,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, Oid graph_oid = InvalidOid; int64 vle_grammar_node_id = 0; bool use_cache = false; + graphid vertex_id_arg; /* * Get the VLE grammar node id, if it exists. Remember, we overload the @@ -658,9 +814,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, if (PG_NARGS() == 8) { /* get the VLE grammar node id */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(7), - AGTV_INTEGER, true); - vle_grammar_node_id = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(7), + AGTV_INTEGER, true, &agtv_temp, + &agtv_temp_needs_free); + vle_grammar_node_id = agtv_temp.val.int_value; /* we are using the VLE local context cache, so set it */ use_cache = true; @@ -683,10 +840,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, */ /* get and update the start vertex id */ - agtv_temp = get_vle_vertex_or_id_arg( - fcinfo, 1, - "start vertex argument must be a vertex or the integer id"); - if (agtv_temp == NULL) + if (!get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { /* if there are no more vertices to process, return NULL */ if (vlelctx->next_vertex == NULL) @@ -699,20 +856,20 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - vlelctx->vsid = agtv_temp->val.int_value; + vlelctx->vsid = vertex_id_arg; } /* get and update the end vertex id */ - agtv_temp = get_vle_vertex_or_id_arg( - fcinfo, 2, - "end vertex argument must be a vertex or the integer id"); - if (agtv_temp == NULL) + if (!get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { vlelctx->veid = 0; } else { - vlelctx->veid = agtv_temp->val.int_value; + vlelctx->veid = vertex_id_arg; } vlelctx->is_dirty = true; @@ -746,21 +903,22 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } /* get the graph name - this is a required argument */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(0), - AGTV_STRING, true); + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(0), + AGTV_STRING, true, &agtv_temp, + &agtv_temp_needs_free); /* get the graph oid before copying the graph name into VLE state */ - graph_oid = get_cached_vle_graph_oid(agtv_temp->val.string.val, - agtv_temp->val.string.len); + graph_oid = get_cached_vle_graph_oid(agtv_temp.val.string.val, + agtv_temp.val.string.len); - graph_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + graph_name = pnstrdup(agtv_temp.val.string.val, + agtv_temp.val.string.len); /* * Create or retrieve the GRAPH global context for this graph. This function * will also purge off invalidated contexts. */ - ggctx = manage_GRAPH_global_contexts_len(agtv_temp->val.string.val, - agtv_temp->val.string.len, + ggctx = manage_GRAPH_global_contexts_len(agtv_temp.val.string.val, + agtv_temp.val.string.len, graph_oid); /* allocate and initialize local VLE context */ @@ -795,10 +953,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, * which path function is used. If a start vertex isn't provided, we * retrieve them incrementally from the vertices list. */ - agtv_temp = get_vle_vertex_or_id_arg( - fcinfo, 1, - "start vertex argument must be a vertex or the integer id"); - if (agtv_temp == NULL) + if (!get_vle_vertex_or_id_arg( + fcinfo, 1, + "start vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { /* set _TO */ vlelctx->path_function = VLE_FUNCTION_PATHS_TO; @@ -810,17 +968,17 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - vlelctx->vsid = agtv_temp->val.int_value; + vlelctx->vsid = vertex_id_arg; } /* * Get the end vertex id - this is an optional parameter and determines * which path function is used. */ - agtv_temp = get_vle_vertex_or_id_arg( - fcinfo, 2, - "end vertex argument must be a vertex or the integer id"); - if (agtv_temp == NULL) + if (!get_vle_vertex_or_id_arg( + fcinfo, 2, + "end vertex argument must be a vertex or the integer id", + &vertex_id_arg)) { if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) { @@ -835,15 +993,16 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, else { vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; - vlelctx->veid = agtv_temp->val.int_value; + vlelctx->veid = vertex_id_arg; } /* get the VLE edge prototype */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(3), - AGTV_EDGE, true); + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(3), + AGTV_EDGE, true, &agtv_temp, + &agtv_temp_needs_free); /* get the edge prototype's property conditions */ - agtv_object = AGTYPE_EDGE_GET_PROPERTIES(agtv_temp); + agtv_object = AGTYPE_EDGE_GET_PROPERTIES(&agtv_temp); agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); /* store the properties as an agtype */ @@ -856,26 +1015,26 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, vlelctx->edge_property_constraint_hash = datum_image_hash(d_edge_property_constraint, false, -1); /* get the edge prototype's label name */ - agtv_temp = AGTYPE_EDGE_GET_LABEL(agtv_temp); - if (agtv_temp->type == AGTV_STRING && - agtv_temp->val.string.len != 0) + agtv_object = AGTYPE_EDGE_GET_LABEL(&agtv_temp); + if (agtv_object->type == AGTV_STRING && + agtv_object->val.string.len != 0) { label_cache_data *label_cache; char label_name_buf[NAMEDATALEN]; char *label_name; bool free_label_name = false; - if (agtv_temp->val.string.len < NAMEDATALEN) + if (agtv_object->val.string.len < NAMEDATALEN) { - memcpy(label_name_buf, agtv_temp->val.string.val, - agtv_temp->val.string.len); - label_name_buf[agtv_temp->val.string.len] = '\0'; + memcpy(label_name_buf, agtv_object->val.string.val, + agtv_object->val.string.len); + label_name_buf[agtv_object->val.string.len] = '\0'; label_name = label_name_buf; } else { - label_name = pnstrdup(agtv_temp->val.string.val, - agtv_temp->val.string.len); + label_name = pnstrdup(agtv_object->val.string.val, + agtv_object->val.string.len); free_label_name = true; } @@ -895,6 +1054,12 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, vlelctx->edge_label_name_oid = InvalidOid; } + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + agtv_temp_needs_free = false; + } + /* get the left range index */ if (PG_ARGISNULL(4)) { @@ -909,9 +1074,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", agt_arg, AGTV_INTEGER, - true); - vlelctx->lidx = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", agt_arg, AGTV_INTEGER, + true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->lidx = agtv_temp.val.int_value; } } @@ -931,16 +1097,18 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - agtv_temp = get_agtype_value("age_vle", agt_arg, AGTV_INTEGER, - true); - vlelctx->uidx = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", agt_arg, AGTV_INTEGER, + true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->uidx = agtv_temp.val.int_value; vlelctx->uidx_infinite = false; } } /* get edge direction */ - agtv_temp = get_agtype_value("age_vle", AG_GET_ARG_AGTYPE_P(6), - AGTV_INTEGER, true); - vlelctx->edge_direction = agtv_temp->val.int_value; + get_vle_scalar_arg_no_copy("age_vle", AG_GET_ARG_AGTYPE_P(6), + AGTV_INTEGER, true, &agtv_temp, + &agtv_temp_needs_free); + vlelctx->edge_direction = agtv_temp.val.int_value; /* create the local state hashtable */ create_VLE_local_state_hashtable(vlelctx); @@ -2225,7 +2393,6 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) agtype *agt_arg_vpc = NULL; agtype *edge_id = NULL; agtype *pos_agt = NULL; - agtype_value *id, *position; VLE_path_container *vle_path = NULL; graphid *array = NULL; bool vle_is_on_left = false; @@ -2281,23 +2448,8 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) { /* Get the edge id we are checking the end of the list too */ edge_id = AG_GET_ARG_AGTYPE_P(1); - if (!AGT_ROOT_IS_SCALAR(edge_id)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 2 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - id = get_ith_agtype_value_from_container(&edge_id->root, 0); - - if (id->type != AGTV_INTEGER) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 2 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - gid = id->val.int_value; + gid = get_agtype_scalar_graphid_arg(edge_id, + "argument 2 of age_match_vle_edge_to_edge_qual must be an integer"); } else if (type1 == GRAPHIDOID) { @@ -2312,23 +2464,8 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) pos_agt = AG_GET_ARG_AGTYPE_P(2); - if (!AGT_ROOT_IS_SCALAR(pos_agt)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 3 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - position = get_ith_agtype_value_from_container(&pos_agt->root, 0); - - if (position->type != AGTV_BOOL) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument 3 of age_match_vle_edge_to_edge_qual must be an integer"))); - } - - vle_is_on_left = position->val.boolean; + vle_is_on_left = get_agtype_scalar_bool_arg(pos_agt, + "argument 3 of age_match_vle_edge_to_edge_qual must be an integer"); if (vle_is_on_left) { @@ -2460,7 +2597,6 @@ Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) agtype *agt_arg_vsid = NULL; agtype *agt_arg_veid = NULL; agtype *agt_arg_path = NULL; - agtype_value *agtv_temp = NULL; graphid vsid = 0; graphid veid = 0; graphid *gida = NULL; @@ -2536,11 +2672,8 @@ Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) if (!is_agtype_null(agt_arg_vsid)) { - agtv_temp = - get_ith_agtype_value_from_container(&agt_arg_vsid->root, 0); - - Assert(agtv_temp->type == AGTV_INTEGER); - vsid = agtv_temp->val.int_value; + vsid = get_agtype_scalar_graphid_arg(agt_arg_vsid, + "match_vle_terminal_edge() argument 1 must be an agtype integer or a graphid"); } else { @@ -2565,10 +2698,8 @@ Datum age_match_vle_terminal_edge(PG_FUNCTION_ARGS) if (!is_agtype_null(agt_arg_veid)) { - agtv_temp = get_ith_agtype_value_from_container(&agt_arg_veid->root, - 0); - Assert(agtv_temp->type == AGTV_INTEGER); - veid = agtv_temp->val.int_value; + veid = get_agtype_scalar_graphid_arg(agt_arg_veid, + "match_vle_terminal_edge() argument 2 must be an agtype integer or a graphid"); } else { @@ -2598,7 +2729,9 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) agtype_in_state result; agtype_value agtv_zero; agtype_value agtv_nstr; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; + bool agtv_temp_needs_free = false; + agtype *agt_result; /* create an agtype_value integer 0 */ agtv_zero.type = AGTV_INTEGER; @@ -2624,10 +2757,11 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) string_to_agtype_value("label")); if (!PG_ARGISNULL(0)) { - agtv_temp = get_agtype_value("build_vle_match_edge", - AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true); + get_vle_scalar_arg_no_copy("build_vle_match_edge", + AG_GET_ARG_AGTYPE_P(0), AGTV_STRING, true, + &agtv_temp, &agtv_temp_needs_free); result.res = push_agtype_value(&result.parse_state, WAGT_VALUE, - agtv_temp); + &agtv_temp); } else { @@ -2674,7 +2808,13 @@ Datum age_build_vle_match_edge(PG_FUNCTION_ARGS) result.res->type = AGTV_EDGE; - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + agt_result = agtype_value_to_agtype(result.res); + if (agtv_temp_needs_free) + { + pfree_agtype_value_content(&agtv_temp); + } + + PG_RETURN_POINTER(agt_result); } PG_FUNCTION_INFO_V1(_ag_enforce_edge_uniqueness2); @@ -2962,22 +3102,35 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) /* if it is a regular AGTYPE scalar */ else if (AGT_ROOT_IS_SCALAR(agt_i)) { - agtype_value *agtv_id = NULL; + agtype_value agtv_id; int64 *value = NULL; bool found = false; + bool id_needs_free = false; + bool id_found; graphid edge_id = 0; - agtv_id = get_ith_agtype_value_from_container(&agt_i->root, 0); + id_found = get_ith_agtype_value_from_container_no_copy( + &agt_i->root, 0, &agtv_id, &id_needs_free); + Assert(id_found); - if (agtv_id->type != AGTV_INTEGER) + if (agtv_id.type != AGTV_INTEGER) { + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("_ag_enforce_edge_uniqueness parameter %d must resolve to an agtype integer", i))); } - edge_id = agtv_id->val.int_value; + edge_id = agtv_id.val.int_value; + + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } /* insert the edge_id */ value = (int64 *)hash_search(exists_hash, (void *)&edge_id, diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 140539fbd..b2792ebe7 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -165,6 +165,15 @@ static void add_indent(StringInfo out, bool indent, int level); static void cannot_cast_agtype_value(enum agtype_value_type type, const char *sqltype); static bool agtype_extract_scalar(agtype_container *agtc, agtype_value *res); +static void get_scalar_agtype_value_no_copy(agtype *agt, + agtype_value *value, + bool *needs_free); +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free); +static text *get_agtype_scalar_string_as_text_no_copy(agtype *agt, + const char *funcname, + bool *is_null); +static int64 get_agtype_scalar_integer_no_copy(agtype *agt, + const char *funcname); static agtype_value *execute_array_access_operator(agtype *array, agtype_value *array_value, agtype *array_index); @@ -255,6 +264,95 @@ void pfree_if_not_null(void *ptr) } } +static void get_scalar_agtype_value_no_copy(agtype *agt, + agtype_value *value, + bool *needs_free) +{ + bool found; + + Assert(AGT_ROOT_IS_SCALAR(agt)); + + found = get_ith_agtype_value_from_container_no_copy(&agt->root, 0, value, + needs_free); + Assert(found); +} + +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free) +{ + if (needs_free) + { + pfree_agtype_value_content(value); + } +} + +static text *get_agtype_scalar_string_as_text_no_copy(agtype *agt, + const char *funcname, + bool *is_null) +{ + agtype_value value; + bool needs_free = false; + text *result; + + if (!AGT_ROOT_IS_SCALAR(agt)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports scalar arguments", funcname))); + } + + get_scalar_agtype_value_no_copy(agt, &value, &needs_free); + + if (value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&value, needs_free); + *is_null = true; + return NULL; + } + + if (value.type != AGTV_STRING) + { + free_agtype_value_no_copy(&value, needs_free); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s unsupported argument agtype %d", funcname, + value.type))); + } + + result = cstring_to_text_with_len(value.val.string.val, + value.val.string.len); + free_agtype_value_no_copy(&value, needs_free); + *is_null = false; + + return result; +} + +static int64 get_agtype_scalar_integer_no_copy(agtype *agt, + const char *funcname) +{ + agtype_value value; + bool needs_free = false; + int64 result; + + if (!AGT_ROOT_IS_SCALAR(agt)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s only supports scalar arguments", funcname))); + } + + get_scalar_agtype_value_no_copy(agt, &value, &needs_free); + + if (value.type != AGTV_INTEGER) + { + free_agtype_value_no_copy(&value, needs_free); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("%s unsupported argument agtype %d", funcname, + value.type))); + } + + result = value.val.int_value; + free_agtype_value_no_copy(&value, needs_free); + + return result; +} + static bool agtype_string_contains(char *haystack, int haystack_len, char *needle, int needle_len) { @@ -3514,8 +3612,9 @@ PG_FUNCTION_INFO_V1(agtype_to_text); Datum agtype_to_text(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; text *text_value; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -3536,11 +3635,11 @@ Datum agtype_to_text(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); - PG_FREE_IF_COPY(arg_agt, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); - text_value = agtype_value_to_text(arg_value, true); - pfree_agtype_value(arg_value); + text_value = agtype_value_to_text(&arg_value, true); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); if (text_value == NULL) { @@ -3737,45 +3836,55 @@ static agtype_value *execute_map_access_operator(agtype *map, agtype_value *map_value, agtype *key) { - agtype_value *key_value; + agtype_value key_value; char *key_str; int key_len = 0; + bool key_needs_free = false; + agtype_value *result; /* get the key from the container */ - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - switch (key_value->type) + switch (key_value.type) { case AGTV_NULL: + free_agtype_value_no_copy(&key_value, key_needs_free); return NULL; case AGTV_INTEGER: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_INTEGER is not a valid key type"))); break; case AGTV_FLOAT: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_FLOAT is not a valid key type"))); break; case AGTV_NUMERIC: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_NUMERIC is not a valid key type"))); break; case AGTV_BOOL: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("AGTV_BOOL is not a valid key type"))); break; case AGTV_STRING: - key_str = key_value->val.string.val; - key_len = key_value->val.string.len; + key_str = key_value.val.string.val; + key_len = key_value.val.string.len; break; default: + free_agtype_value_no_copy(&key_value, key_needs_free); ereport(ERROR, (errmsg("unknown agtype scalar type"))); break; } - return execute_map_access_operator_internal(map, map_value, key_str, - key_len); + result = execute_map_access_operator_internal(map, map_value, key_str, + key_len); + free_agtype_value_no_copy(&key_value, key_needs_free); + return result; } static agtype_value *execute_map_access_operator_internal( @@ -3821,31 +3930,34 @@ static agtype_value *execute_array_access_operator(agtype *array, agtype_value *array_value, agtype *array_index) { - agtype_value *array_index_value = NULL; + agtype_value array_index_value; + bool array_index_needs_free = false; agtype_value *result = NULL; /* unpack the array index value */ - array_index_value = get_ith_agtype_value_from_container(&array_index->root, - 0); + (void)get_ith_agtype_value_from_container_no_copy(&array_index->root, 0, + &array_index_value, + &array_index_needs_free); /* if AGTV_NULL return NULL */ - if (array_index_value->type == AGTV_NULL) + if (array_index_value.type == AGTV_NULL) { - pfree_agtype_value(array_index_value); + free_agtype_value_no_copy(&array_index_value, + array_index_needs_free); return NULL; } /* index must be an integer */ - if (array_index_value->type != AGTV_INTEGER) + if (array_index_value.type != AGTV_INTEGER) { ereport(ERROR, (errmsg("array index must resolve to an integer value"))); } result = execute_array_access_operator_internal(array, array_value, - array_index_value->val.int_value); + array_index_value.val.int_value); - pfree_agtype_value(array_index_value); + free_agtype_value_no_copy(&array_index_value, array_index_needs_free); return result; } @@ -4230,36 +4342,37 @@ Datum agtype_object_field_agtype(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(key)) { - agtype_value *key_value; + agtype_value key_value; + bool key_needs_free = false; - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - if (key_value->type == AGTV_INTEGER || - key_value->type == AGTV_STRING) + if (key_value.type == AGTV_INTEGER || + key_value.type == AGTV_STRING) { Datum retval = 0; - if (key_value->type == AGTV_INTEGER) + if (key_value.type == AGTV_INTEGER) { retval = agtype_array_element_impl(fcinfo, agt, - key_value->val.int_value, + key_value.val.int_value, false); } - else if (key_value->type == AGTV_STRING) + else if (key_value.type == AGTV_STRING) { retval = agtype_object_field_impl(fcinfo, agt, - key_value->val.string.val, - key_value->val.string.len, + key_value.val.string.val, + key_value.val.string.len, false); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); PG_FREE_IF_COPY(agt, 0); PG_FREE_IF_COPY(key, 1); PG_RETURN_POINTER((const void*) retval); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); } PG_FREE_IF_COPY(agt, 0); @@ -4276,35 +4389,36 @@ Datum agtype_object_field_text_agtype(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(key)) { - agtype_value *key_value; + agtype_value key_value; + bool key_needs_free = false; - key_value = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, &key_value, &key_needs_free); - if (key_value->type == AGTV_INTEGER || key_value->type == AGTV_STRING) + if (key_value.type == AGTV_INTEGER || key_value.type == AGTV_STRING) { Datum retval = 0; - if (key_value->type == AGTV_INTEGER) + if (key_value.type == AGTV_INTEGER) { retval = agtype_array_element_impl(fcinfo, agt, - key_value->val.int_value, + key_value.val.int_value, true); } - else if (key_value->type == AGTV_STRING) + else if (key_value.type == AGTV_STRING) { retval = agtype_object_field_impl(fcinfo, agt, - key_value->val.string.val, - key_value->val.string.len, + key_value.val.string.val, + key_value.val.string.len, true); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); PG_FREE_IF_COPY(agt, 0); PG_FREE_IF_COPY(key, 1); PG_RETURN_POINTER((const void*) retval); } - pfree_agtype_value(key_value); + free_agtype_value_no_copy(&key_value, key_needs_free); } PG_FREE_IF_COPY(agt, 0); @@ -4389,6 +4503,8 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) int nargs = 0; agtype *container = NULL; agtype_value *container_value = NULL; + agtype_value scalar_container_value; + bool scalar_container_needs_free = false; agtype *result = NULL; int i = 0; bool using_fast_args = false; @@ -4430,8 +4546,11 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* handle scalar (vertex or edge) */ else if (AGT_ROOT_IS_SCALAR(container)) { - container_value = get_ith_agtype_value_from_container( - &container->root, 0); + (void)get_ith_agtype_value_from_container_no_copy( + &container->root, 0, &scalar_container_value, + &scalar_container_needs_free); + container_value = &scalar_container_value; + if (container_value->type != AGTV_EDGE && container_value->type != AGTV_VERTEX) { @@ -4490,10 +4609,14 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) if (container_value == NULL || container_value->type == AGTV_NULL) { + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); PG_RETURN_NULL(); } result = agtype_value_to_agtype(container_value); + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); return AGTYPE_P_GET_DATUM(result); } @@ -4588,8 +4711,10 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* if it is a scalar, open it and pull out the value */ else if (AGT_ROOT_IS_SCALAR(container)) { - container_value = get_ith_agtype_value_from_container(&container->root, - 0); + (void)get_ith_agtype_value_from_container_no_copy( + &container->root, 0, &scalar_container_value, + &scalar_container_needs_free); + container_value = &scalar_container_value; /* it must be either a vertex or an edge */ if (container_value->type != AGTV_EDGE && @@ -4671,6 +4796,14 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* for NULL values return NULL */ if (container_value == NULL || container_value->type == AGTV_NULL) { + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } PG_RETURN_NULL(); } @@ -4687,6 +4820,8 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* serialize and return the result */ result = agtype_value_to_agtype(container_value); + free_agtype_value_no_copy(&scalar_container_value, + scalar_container_needs_free); return AGTYPE_P_GET_DATUM(result); } @@ -4697,8 +4832,12 @@ PG_FUNCTION_INFO_V1(agtype_access_slice); */ Datum agtype_access_slice(PG_FUNCTION_ARGS) { - agtype_value *lidx_value = NULL; - agtype_value *uidx_value = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value *lidx_value_ptr = NULL; + agtype_value *uidx_value_ptr = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; agtype_in_state result; agtype *agt_result = NULL; agtype *agt_array = NULL; @@ -4753,14 +4892,18 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) else { agt_lidx = AG_GET_ARG_AGTYPE_P(1); - lidx_value = get_ith_agtype_value_from_container(&agt_lidx->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_lidx->root, 0, + &lidx_value, + &lidx_needs_free); + lidx_value_ptr = &lidx_value; /* * Under Cypher null-propagation semantics, list[a..b] is null when * either bound is null. Return null directly instead of silently * treating AGTV_NULL as an omitted bound. */ - if (lidx_value->type == AGTV_NULL) + if (lidx_value_ptr->type == AGTV_NULL) { + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); PG_RETURN_NULL(); } } @@ -4773,39 +4916,44 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) else { agt_uidx = AG_GET_ARG_AGTYPE_P(2); - uidx_value = get_ith_agtype_value_from_container(&agt_uidx->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_uidx->root, 0, + &uidx_value, + &uidx_needs_free); + uidx_value_ptr = &uidx_value; /* Symmetric to the lower bound: null propagates to a null result. */ - if (uidx_value->type == AGTV_NULL) + if (uidx_value_ptr->type == AGTV_NULL) { + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); + free_agtype_value_no_copy(uidx_value_ptr, uidx_needs_free); PG_RETURN_NULL(); } } /* if both indices are NULL (AGTV_NULL) return an error */ - if (lidx_value == NULL && uidx_value == NULL) + if (lidx_value_ptr == NULL && uidx_value_ptr == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("slice start and/or end is required"))); } /* key must be an integer or NULL */ - if ((lidx_value != NULL && lidx_value->type != AGTV_INTEGER) || - (uidx_value != NULL && uidx_value->type != AGTV_INTEGER)) + if ((lidx_value_ptr != NULL && lidx_value_ptr->type != AGTV_INTEGER) || + (uidx_value_ptr != NULL && uidx_value_ptr->type != AGTV_INTEGER)) { ereport(ERROR, (errmsg("array slices must resolve to an integer value"))); } /* set indices if not already set */ - if (lidx_value) + if (lidx_value_ptr) { - lower_index = lidx_value->val.int_value; - pfree_agtype_value(lidx_value); + lower_index = lidx_value_ptr->val.int_value; + free_agtype_value_no_copy(lidx_value_ptr, lidx_needs_free); } - if (uidx_value) + if (uidx_value_ptr) { - upper_index = uidx_value->val.int_value; - pfree_agtype_value(uidx_value); + upper_index = uidx_value_ptr->val.int_value; + free_agtype_value_no_copy(uidx_value_ptr, uidx_needs_free); } /* adjust for negative and out of bounds index values */ @@ -4997,21 +5145,27 @@ Datum agtype_string_match_starts_with(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - if (lhs_value->val.string.len < rhs_value->val.string.len) + if (lhs_value.val.string.len < rhs_value.val.string.len) { result = false; } - else if (memcmp(lhs_value->val.string.val, - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp(lhs_value.val.string.val, + rhs_value.val.string.val, + rhs_value.val.string.len) == 0) { result = true; } @@ -5020,8 +5174,14 @@ Datum agtype_string_match_starts_with(PG_FUNCTION_ARGS) result = false; } } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); + } } else { @@ -5047,23 +5207,29 @@ Datum agtype_string_match_ends_with(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - if (lhs_value->val.string.len < rhs_value->val.string.len) + if (lhs_value.val.string.len < rhs_value.val.string.len) { result = false; } - else if (memcmp((lhs_value->val.string.val + - lhs_value->val.string.len - - rhs_value->val.string.len), - rhs_value->val.string.val, - rhs_value->val.string.len) == 0) + else if (memcmp((lhs_value.val.string.val + + lhs_value.val.string.len - + rhs_value.val.string.len), + rhs_value.val.string.val, + rhs_value.val.string.len) == 0) { result = true; } @@ -5072,8 +5238,14 @@ Datum agtype_string_match_ends_with(PG_FUNCTION_ARGS) result = false; } } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); + } } else { @@ -5099,21 +5271,33 @@ Datum agtype_string_match_contains(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs)) { - agtype_value *lhs_value; - agtype_value *rhs_value; + agtype_value lhs_value; + agtype_value rhs_value; + bool lhs_needs_free = false; + bool rhs_needs_free = false; - lhs_value = get_ith_agtype_value_from_container(&lhs->root, 0); - rhs_value = get_ith_agtype_value_from_container(&rhs->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&lhs->root, 0, + &lhs_value, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, + &rhs_value, + &rhs_needs_free); - if (lhs_value->type == AGTV_STRING && rhs_value->type == AGTV_STRING) + if (lhs_value.type == AGTV_STRING && rhs_value.type == AGTV_STRING) { - result = agtype_string_contains(lhs_value->val.string.val, - lhs_value->val.string.len, - rhs_value->val.string.val, - rhs_value->val.string.len); + result = agtype_string_contains(lhs_value.val.string.val, + lhs_value.val.string.len, + rhs_value.val.string.val, + rhs_value.val.string.len); + } + if (lhs_needs_free) + { + pfree_agtype_value_content(&lhs_value); + } + if (rhs_needs_free) + { + pfree_agtype_value_content(&rhs_value); } - pfree_agtype_value(lhs_value); - pfree_agtype_value(rhs_value); } else { @@ -5214,11 +5398,12 @@ PG_FUNCTION_INFO_V1(agtype_typecast_numeric); Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum numd; char *string = NULL; agtype *result = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5238,33 +5423,31 @@ Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); - - /* we don't need to agtype arg anymore */ - PG_FREE_IF_COPY(arg_agt, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: numd = DirectFunctionCall1(int8_numeric, - Int64GetDatum(arg_value->val.int_value)); + Int64GetDatum(arg_value.val.int_value)); break; case AGTV_FLOAT: numd = DirectFunctionCall1(float8_numeric, - Float8GetDatum(arg_value->val.float_value)); + Float8GetDatum(arg_value.val.float_value)); break; case AGTV_NUMERIC: /* it is already a numeric so just return it */ - result = agtype_value_to_agtype(arg_value); - pfree_agtype_value(arg_value); + result = agtype_value_to_agtype(&arg_value); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_POINTER(result); break; /* this allows string numbers and NaN */ case AGTV_STRING: /* we need a null terminated string */ - string = pnstrdup(arg_value->val.string.val, - arg_value->val.string.len); + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); /* pass the string to the numeric in function for conversion */ numd = DirectFunctionCall3(numeric_in, CStringGetDatum(string), @@ -5276,13 +5459,16 @@ Datum agtype_typecast_numeric(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to a numeric */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } - pfree_agtype_value(arg_value); + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); /* fill in and return our result */ result_value.type = AGTV_NUMERIC; @@ -5301,10 +5487,11 @@ PG_FUNCTION_INFO_V1(agtype_typecast_int); Datum agtype_typecast_int(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; char *string = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5324,36 +5511,44 @@ Datum agtype_typecast_int(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_FLOAT: d = DirectFunctionCall1(dtoi8, - Float8GetDatum(arg_value->val.float_value)); + Float8GetDatum(arg_value.val.float_value)); break; case AGTV_NUMERIC: d = DirectFunctionCall1(numeric_int8, - NumericGetDatum(arg_value->val.numeric)); + NumericGetDatum(arg_value.val.numeric)); break; case AGTV_BOOL: d = DirectFunctionCall1(bool_int4, - BoolGetDatum(arg_value->val.boolean)); + BoolGetDatum(arg_value.val.boolean)); break; case AGTV_STRING: /* we need a null terminated string */ - string = pnstrdup(arg_value->val.string.val, - arg_value->val.string.len); + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); d = DirectFunctionCall1(int8in, CStringGetDatum(string)); /* free the string */ @@ -5362,12 +5557,17 @@ Datum agtype_typecast_int(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to an int */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_INTEGER; result_value.val.int_value = DatumGetInt64(d); @@ -5382,9 +5582,10 @@ PG_FUNCTION_INFO_V1(agtype_typecast_bool); Datum agtype_typecast_bool(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5404,31 +5605,44 @@ Datum agtype_typecast_bool(PG_FUNCTION_ARGS) } /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_BOOL: - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_INTEGER: - d = BoolGetDatum(arg_value->val.int_value != 0); + d = BoolGetDatum(arg_value.val.int_value != 0); break; /* what was given doesn't cast to a bool */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be an integer or a boolean"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_BOOL; result_value.val.boolean = DatumGetBool(d); @@ -5443,10 +5657,11 @@ PG_FUNCTION_INFO_V1(agtype_typecast_float); Datum agtype_typecast_float(PG_FUNCTION_ARGS) { agtype *arg_agt; - agtype_value *arg_value; + agtype_value arg_value; agtype_value result_value; Datum d; char *string = NULL; + bool value_needs_free = false; /* get the agtype equivalence of any convertable input type */ arg_agt = get_one_agtype_from_variadic_args(fcinfo, 0, 1); @@ -5462,30 +5677,40 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) errmsg("typecast argument must be a scalar value"))); /* get the arg parameter */ - arg_value = get_ith_agtype_value_from_container(&arg_agt->root, 0); + get_scalar_agtype_value_no_copy(arg_agt, &arg_value, &value_needs_free); /* check for agtype null */ - if (arg_value->type == AGTV_NULL) + if (arg_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); PG_RETURN_NULL(); + } /* the input type drives the casting */ - switch(arg_value->type) + switch(arg_value.type) { case AGTV_INTEGER: - d = Float8GetDatum((float8)arg_value->val.int_value); + d = Float8GetDatum((float8)arg_value.val.int_value); break; case AGTV_FLOAT: /* it is already a float so just return it */ - PG_RETURN_POINTER(agtype_value_to_agtype(arg_value)); + { + agtype *result = agtype_value_to_agtype(&arg_value); + + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + PG_RETURN_POINTER(result); break; + } case AGTV_NUMERIC: d = DirectFunctionCall1(numeric_float8, - NumericGetDatum(arg_value->val.numeric)); + NumericGetDatum(arg_value.val.numeric)); break; /* this allows string numbers, NaN, Infinity, and -Infinity */ case AGTV_STRING: /* we need a null terminated string */ - string = pnstrdup(arg_value->val.string.val, - arg_value->val.string.len); + string = pnstrdup(arg_value.val.string.val, + arg_value.val.string.len); d = DirectFunctionCall1(float8in, CStringGetDatum(string)); /* free the string */ @@ -5494,12 +5719,17 @@ Datum agtype_typecast_float(PG_FUNCTION_ARGS) break; /* what was given doesn't cast to a float */ default: + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast expression must be a number or a string"))); break; } + free_agtype_value_no_copy(&arg_value, value_needs_free); + PG_FREE_IF_COPY(arg_agt, 0); + /* set the result type and return our result */ result_value.type = AGTV_FLOAT; result_value.val.float_value = DatumGetFloat8(d); @@ -5684,7 +5914,9 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) { agtype *arg_agt = NULL; agtype_in_state path; - agtype_value *agtv_element = NULL; + agtype_value *path_values = NULL; + bool *path_needs_free = NULL; + agtype *result; int count = 0; int i = 0; @@ -5709,6 +5941,9 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); + path_values = palloc0(sizeof(agtype_value) * count); + path_needs_free = palloc0(sizeof(bool) * count); + /* create an agtype array */ memset(&path, 0, sizeof(agtype_in_state)); path.res = push_agtype_value(&path.parse_state, WAGT_BEGIN_ARRAY, NULL); @@ -5720,36 +5955,51 @@ Datum agtype_typecast_path(PG_FUNCTION_ARGS) for (i = 0; i+1 < count; i+=2) { /* get a potential vertex, check it, then add it */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i); - if (agtv_element == NULL || agtv_element->type != AGTV_VERTEX) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i, + &path_values[i], + &path_needs_free[i]); + if (path_values[i].type != AGTV_VERTEX) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i]); /* get a potential edge, check it, then add it */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i+1); - if (agtv_element == NULL || agtv_element->type != AGTV_EDGE) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i+1, + &path_values[i+1], + &path_needs_free[i+1]); + if (path_values[i+1].type != AGTV_EDGE) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i+1]); } /* validate the last element is a vertex, add it if it is, fail otherwise */ - agtv_element = get_ith_agtype_value_from_container(&arg_agt->root, i); - if (agtv_element == NULL || agtv_element->type != AGTV_VERTEX) + (void)get_ith_agtype_value_from_container_no_copy(&arg_agt->root, i, + &path_values[i], + &path_needs_free[i]); + if (path_values[i].type != AGTV_VERTEX) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("typecast argument is not a valid path"))); - push_agtype_value(&path.parse_state, WAGT_ELEM, agtv_element); + push_agtype_value(&path.parse_state, WAGT_ELEM, &path_values[i]); /* close the array */ path.res = push_agtype_value(&path.parse_state, WAGT_END_ARRAY, NULL); /* set it to a path */ path.res->type = AGTV_PATH; - PG_RETURN_POINTER(agtype_value_to_agtype(path.res)); + result = agtype_value_to_agtype(path.res); + + for (i = 0; i < count; i++) + { + free_agtype_value_no_copy(&path_values[i], path_needs_free[i]); + } + pfree(path_values); + pfree(path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_id); @@ -5757,8 +6007,10 @@ PG_FUNCTION_INFO_V1(age_id); Datum age_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5770,29 +6022,34 @@ Datum age_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_VERTEX && agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_VERTEX && agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("id() argument must be a vertex, an edge or null"))); + } /* * Direct field access optimization: id is at a fixed index for both * vertex and edge objects due to key length sorting. */ - if (agtv_object->type == AGTV_VERTEX) + if (agtv_object.type == AGTV_VERTEX) { - agtv_result = AGTYPE_VERTEX_GET_ID(agtv_object); + agtv_result = AGTYPE_VERTEX_GET_ID(&agtv_object); } - else if (agtv_object->type == AGTV_EDGE) + else if (agtv_object.type == AGTV_EDGE) { - agtv_result = AGTYPE_EDGE_GET_ID(agtv_object); + agtv_result = AGTYPE_EDGE_GET_ID(&agtv_object); } else { @@ -5801,7 +6058,10 @@ Datum age_id(PG_FUNCTION_ARGS) errmsg("id() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_start_id); @@ -5809,8 +6069,10 @@ PG_FUNCTION_INFO_V1(age_start_id); Datum age_start_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5822,25 +6084,33 @@ Datum age_start_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("start_id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("start_id() argument must be an edge or null"))); + } /* * Direct field access optimization: start_id is at index 3 for edge * objects due to key length sorting (id=0, label=1, end_id=2, start_id=3). */ - agtv_result = AGTYPE_EDGE_GET_START_ID(agtv_object); + agtv_result = AGTYPE_EDGE_GET_START_ID(&agtv_object); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_end_id); @@ -5848,8 +6118,10 @@ PG_FUNCTION_INFO_V1(age_end_id); Datum age_end_id(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -5861,25 +6133,33 @@ Datum age_end_id(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("end_id() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("end_id() argument must be an edge or null"))); + } /* * Direct field access optimization: end_id is at index 2 for edge * objects due to key length sorting (id=0, label=1, end_id=2). */ - agtv_result = AGTYPE_EDGE_GET_END_ID(agtv_object); + agtv_result = AGTYPE_EDGE_GET_END_ID(&agtv_object); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } /* @@ -6120,7 +6400,8 @@ PG_FUNCTION_INFO_V1(age_startnode); Datum age_startnode(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value graph_name_value; + agtype_value edge_value; agtype_value *agtv_value = NULL; char *graph_name; int graph_name_len; @@ -6128,6 +6409,8 @@ Datum age_startnode(PG_FUNCTION_ARGS) Oid label_relation = InvalidOid; graphid start_id; Datum result; + bool graph_name_needs_free = false; + bool edge_needs_free = false; /* we need the graph name */ Assert(PG_ARGISNULL(0) == false); @@ -6140,10 +6423,11 @@ Datum age_startnode(PG_FUNCTION_ARGS) agt_arg = AG_GET_ARG_AGTYPE_P(0); /* it must be a scalar and must be a string */ Assert(AGT_ROOT_IS_SCALAR(agt_arg)); - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); - Assert(agtv_object->type == AGTV_STRING); - graph_name = agtv_object->val.string.val; - graph_name_len = agtv_object->val.string.len; + get_scalar_agtype_value_no_copy(agt_arg, &graph_name_value, + &graph_name_needs_free); + Assert(graph_name_value.type == AGTV_STRING); + graph_name = graph_name_value.val.string.val; + graph_name_len = graph_name_value.val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -6151,23 +6435,30 @@ Datum age_startnode(PG_FUNCTION_ARGS) if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("startNode() argument must resolve to a scalar value"))); - /* get the object */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &edge_value, &edge_needs_free); /* is it an agtype null, return null if it is */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (edge_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (edge_value.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("startNode() argument must be an edge or null"))); + } /* * Get the graphid for start_id. Edge objects have deterministic field * ordering, so use direct access instead of a keyed lookup. */ - agtv_value = AGTYPE_EDGE_GET_START_ID(agtv_object); + agtv_value = AGTYPE_EDGE_GET_START_ID(&edge_value); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); Assert(agtv_value->type == AGTV_INTEGER); @@ -6182,6 +6473,9 @@ Datum age_startnode(PG_FUNCTION_ARGS) result = get_vertex(label_name, label_relation, start_id); + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + return result; } @@ -6190,7 +6484,8 @@ PG_FUNCTION_INFO_V1(age_endnode); Datum age_endnode(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value graph_name_value; + agtype_value edge_value; agtype_value *agtv_value = NULL; char *graph_name; int graph_name_len; @@ -6198,6 +6493,8 @@ Datum age_endnode(PG_FUNCTION_ARGS) Oid label_relation = InvalidOid; graphid end_id; Datum result; + bool graph_name_needs_free = false; + bool edge_needs_free = false; /* we need the graph name */ Assert(PG_ARGISNULL(0) == false); @@ -6210,10 +6507,11 @@ Datum age_endnode(PG_FUNCTION_ARGS) agt_arg = AG_GET_ARG_AGTYPE_P(0); /* it must be a scalar and must be a string */ Assert(AGT_ROOT_IS_SCALAR(agt_arg)); - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); - Assert(agtv_object->type == AGTV_STRING); - graph_name = agtv_object->val.string.val; - graph_name_len = agtv_object->val.string.len; + get_scalar_agtype_value_no_copy(agt_arg, &graph_name_value, + &graph_name_needs_free); + Assert(graph_name_value.type == AGTV_STRING); + graph_name = graph_name_value.val.string.val; + graph_name_len = graph_name_value.val.string.len; /* get the edge */ agt_arg = AG_GET_ARG_AGTYPE_P(1); @@ -6221,23 +6519,30 @@ Datum age_endnode(PG_FUNCTION_ARGS) if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("endNode() argument must resolve to a scalar value"))); - /* get the object */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &edge_value, &edge_needs_free); /* is it an agtype null, return null if it is */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (edge_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (edge_value.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("endNode() argument must be an edge or null"))); + } /* * Get the graphid for end_id. Edge objects have deterministic field * ordering, so use direct access instead of a keyed lookup. */ - agtv_value = AGTYPE_EDGE_GET_END_ID(agtv_object); + agtv_value = AGTYPE_EDGE_GET_END_ID(&edge_value); /* it must not be null and must be an integer */ Assert(agtv_value != NULL); Assert(agtv_value->type == AGTV_INTEGER); @@ -6252,6 +6557,9 @@ Datum age_endnode(PG_FUNCTION_ARGS) result = get_vertex(label_name, label_relation, end_id); + free_agtype_value_no_copy(&edge_value, edge_needs_free); + free_agtype_value_no_copy(&graph_name_value, graph_name_needs_free); + return result; } @@ -6262,6 +6570,9 @@ Datum age_head(PG_FUNCTION_ARGS) agtype *agt_arg = NULL; agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; + agtype_value borrowed_result; + agtype *result; + bool result_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -6304,16 +6615,23 @@ Datum age_head(PG_FUNCTION_ARGS) } /* get the first element of the array */ - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, 0, + &borrowed_result, + &result_needs_free); + agtv_result = &borrowed_result; } /* if it is AGTV_NULL, return null */ if (agtv_result->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(agtv_result, result_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_last); @@ -6323,6 +6641,9 @@ Datum age_last(PG_FUNCTION_ARGS) agtype *agt_arg = NULL; agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; + agtype_value borrowed_result; + agtype *result; + bool result_needs_free = false; int size; /* check for null */ @@ -6370,16 +6691,24 @@ Datum age_last(PG_FUNCTION_ARGS) } /* get the first element of the array */ - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, size-1); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, + size-1, + &borrowed_result, + &result_needs_free); + agtv_result = &borrowed_result; } /* if it is AGTV_NULL, return null */ if (agtv_result->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(agtv_result, result_needs_free); + + PG_RETURN_POINTER(result); } @@ -6464,8 +6793,10 @@ PG_FUNCTION_INFO_V1(age_properties); Datum age_properties(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -6492,18 +6823,19 @@ Datum age_properties(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agt_arg); } - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) + if (agtv_object.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_object, object_needs_free); PG_RETURN_NULL(); } /* check for proper agtype */ - if (agtv_object->type != AGTV_VERTEX && agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_VERTEX && agtv_object.type != AGTV_EDGE) { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("properties() argument must be a vertex, an edge or null"))); } @@ -6513,13 +6845,13 @@ Datum age_properties(PG_FUNCTION_ARGS) * (id=0, label=1, properties=2) and index 4 for edge (id=0, label=1, * end_id=2, start_id=3, properties=4) due to key length sorting. */ - if (agtv_object->type == AGTV_VERTEX) + if (agtv_object.type == AGTV_VERTEX) { - agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(agtv_object); + agtv_result = AGTYPE_VERTEX_GET_PROPERTIES(&agtv_object); } - else if (agtv_object->type == AGTV_EDGE) + else if (agtv_object.type == AGTV_EDGE) { - agtv_result = AGTYPE_EDGE_GET_PROPERTIES(agtv_object); + agtv_result = AGTYPE_EDGE_GET_PROPERTIES(&agtv_object); } else { @@ -6528,7 +6860,10 @@ Datum age_properties(PG_FUNCTION_ARGS) errmsg("properties() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_length); @@ -6536,8 +6871,10 @@ PG_FUNCTION_INFO_V1(age_length); Datum age_length(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_value agtv_result; + agtype *result; + bool path_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -6549,22 +6886,30 @@ Datum age_length(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("length() argument must resolve to a scalar"))); - /* get the path array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* if it is AGTV_NULL, return null */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); PG_RETURN_NULL(); + } /* check for a path */ - if (agtv_path ->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("length() argument must resolve to a path or null"))); + } agtv_result.type = AGTV_INTEGER; - agtv_result.val.int_value = (agtv_path->val.array.num_elems - 1) /2; + agtv_result.val.int_value = (agtv_path.val.array.num_elems - 1) /2; - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + result = agtype_value_to_agtype(&agtv_result); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_toboolean); @@ -6637,7 +6982,8 @@ Datum age_toboolean(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -6646,31 +6992,37 @@ Datum age_toboolean(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toBoolean() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_BOOL) - result = agtv_value->val.boolean; - else if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_BOOL) + result = agtv_value.val.boolean; + else if (agtv_value.type == AGTV_STRING) { - int len = agtv_value->val.string.len; + int len = agtv_value.val.string.len; - string = agtv_value->val.string.val; + string = agtv_value.val.string.val; if (len == 4 && pg_strncasecmp(string, "true", len) == 0) result = true; else if (len == 5 && pg_strncasecmp(string, "false", len) == 0) result = false; else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); + } } - else if (agtv_value->type == AGTV_INTEGER) + else if (agtv_value.type == AGTV_INTEGER) { - result = agtv_value->val.int_value != 0; + result = agtv_value.val.int_value != 0; } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toBoolean() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -6720,8 +7072,14 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* check element's type, it's value, and convert it to boolean if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; bool_elem.type = AGTV_BOOL; switch (elem->type) @@ -6777,6 +7135,8 @@ Datum age_tobooleanlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } /* push the end of the array */ @@ -6841,7 +7201,8 @@ Datum age_tofloat(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -6850,32 +7211,38 @@ Datum age_tofloat(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toFloat() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - result = (float8) agtv_value->val.int_value; + result = (float8) agtv_value.val.int_value; } - else if (agtv_value->type == AGTV_FLOAT) - result = agtv_value->val.float_value; - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_FLOAT) + result = agtv_value.val.float_value; + else if (agtv_value.type == AGTV_NUMERIC) result = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, - NumericGetDatum(agtv_value->val.numeric))); - else if (agtv_value->type == AGTV_STRING) + NumericGetDatum(agtv_value.val.numeric))); + else if (agtv_value.type == AGTV_STRING) { - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); result = float8in_internal_null(string, NULL, "double precision", string, &is_valid); pfree(string); if (!is_valid) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); + } } else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toFloat() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -6931,8 +7298,14 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* TODO: check element's type, it's value, and convert it to float if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; float_elem.type = AGTV_FLOAT; switch (elem->type) @@ -6981,6 +7354,8 @@ Datum age_tofloatlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); @@ -7104,7 +7479,8 @@ Datum age_tointeger(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -7113,26 +7489,28 @@ Datum age_tointeger(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toInteger() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_INTEGER) - result = agtv_value->val.int_value; - else if (agtv_value->type == AGTV_FLOAT) + if (agtv_value.type == AGTV_INTEGER) + result = agtv_value.val.int_value; + else if (agtv_value.type == AGTV_FLOAT) { - float8 f = agtv_value->val.float_value; + float8 f = agtv_value.val.float_value; if (isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } result = (int64) f; } - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_NUMERIC) { float8 f; - Datum num = NumericGetDatum(agtv_value->val.numeric); + Datum num = NumericGetDatum(agtv_value.val.numeric); f = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, num)); @@ -7140,17 +7518,18 @@ Datum age_tointeger(PG_FUNCTION_ARGS) if (isnan(f) || isinf(f) || f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } result = (int64) f; } - else if (agtv_value->type == AGTV_STRING) + else if (agtv_value.type == AGTV_STRING) { char *endptr; /* we need a null terminated cstring */ - string = pnstrdup(agtv_value->val.string.val, - agtv_value->val.string.len); + string = pnstrdup(agtv_value.val.string.val, + agtv_value.val.string.len); /* convert it if it is a regular integer string */ result = strtoi64(string, &endptr, 10); @@ -7172,6 +7551,7 @@ Datum age_tointeger(PG_FUNCTION_ARGS) f < (float8)PG_INT64_MIN || f > (float8)PG_INT64_MAX) { pfree(string); + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } @@ -7184,8 +7564,10 @@ Datum age_tointeger(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toInteger() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* build the result */ @@ -7240,8 +7622,14 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) /* iterate through the list */ for (i = 0; i < count; i++) { + agtype_value elem_value; + bool elem_needs_free = false; + /* TODO: check element's type, it's value, and convert it to integer if possible. */ - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem_value, + &elem_needs_free); + elem = &elem_value; integer_elem.type = AGTV_INTEGER; switch (elem->type) @@ -7324,6 +7712,8 @@ Datum age_tointegerlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_END_ARRAY, NULL); @@ -7360,27 +7750,35 @@ Datum age_size(PG_FUNCTION_ARGS) else if (type == AGTYPEOID) { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + agtype_value agtv_value; + bool value_needs_free = false; + + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_STRING) { - result = agtv_value->val.string.len; + result = agtv_value.val.string.len; } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("size() unsupported argument"))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else if (AGT_ROOT_IS_VPC(agt_arg)) { + agtype_value *agtv_value; + agtv_value = agtv_materialize_vle_edges(agt_arg); result = agtv_value->val.array.num_elems; } @@ -7435,8 +7833,10 @@ PG_FUNCTION_INFO_V1(age_type); Datum age_type(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_object = NULL; + agtype_value agtv_object; agtype_value *agtv_result = NULL; + agtype *result; + bool object_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -7448,24 +7848,32 @@ Datum age_type(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("type() argument must resolve to a scalar value"))); - /* get the object out of the array */ - agtv_object = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_object, &object_needs_free); /* is it an agtype null? */ - if (agtv_object->type == AGTV_NULL) - PG_RETURN_NULL(); + if (agtv_object.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); + PG_RETURN_NULL(); + } /* check for proper agtype */ - if (agtv_object->type != AGTV_EDGE) + if (agtv_object.type != AGTV_EDGE) + { + free_agtype_value_no_copy(&agtv_object, object_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("type() argument must be an edge or null"))); + } - agtv_result = AGTYPE_EDGE_GET_LABEL(agtv_object); + agtv_result = AGTYPE_EDGE_GET_LABEL(&agtv_object); Assert(agtv_result != NULL); Assert(agtv_result->type == AGTV_STRING); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + result = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&agtv_object, object_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_exists); @@ -7517,29 +7925,35 @@ Datum age_isempty(PG_FUNCTION_ARGS) else if (type == AGTYPEOID) { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); - if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_STRING) { - result = agtv_value->val.string.len; + result = agtv_value.val.string.len; } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("isEmpty() unsupported argument, expected a List, Map, or String"))); } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_value = agtv_materialize_vle_edges(agt_arg); - result = agtv_value->val.array.num_elems; + agtype_value *agtv_edges = agtv_materialize_vle_edges(agt_arg); + + result = agtv_edges->val.array.num_elems; } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { @@ -7572,8 +7986,10 @@ PG_FUNCTION_INFO_V1(age_label); Datum age_label(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; + agtype_value agtv_value; agtype_value *label = NULL; + agtype *result; + bool value_needs_free = false; /* check for NULL, NULL is FALSE */ if (PG_ARGISNULL(0)) @@ -7593,11 +8009,12 @@ Datum age_label(PG_FUNCTION_ARGS) } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, &value_needs_free); /* fail if agtype value isn't an edge or vertex */ - if (agtv_value->type != AGTV_VERTEX && agtv_value->type != AGTV_EDGE) + if (agtv_value.type != AGTV_VERTEX && agtv_value.type != AGTV_EDGE) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("label() argument must resolve to an edge or vertex"))); @@ -7607,13 +8024,13 @@ Datum age_label(PG_FUNCTION_ARGS) * Direct field access optimization: label is at a fixed index for both * vertex and edge objects due to key length sorting. */ - if (agtv_value->type == AGTV_VERTEX) + if (agtv_value.type == AGTV_VERTEX) { - label = AGTYPE_VERTEX_GET_LABEL(agtv_value); + label = AGTYPE_VERTEX_GET_LABEL(&agtv_value); } - else if (agtv_value->type == AGTV_EDGE) + else if (agtv_value.type == AGTV_EDGE) { - label = AGTYPE_EDGE_GET_LABEL(agtv_value); + label = AGTYPE_EDGE_GET_LABEL(&agtv_value); } else { @@ -7622,7 +8039,10 @@ Datum age_label(PG_FUNCTION_ARGS) errmsg("label() unexpected argument type"))); } - PG_RETURN_POINTER(agtype_value_to_agtype(label)); + result = agtype_value_to_agtype(label); + free_agtype_value_no_copy(&agtv_value, value_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_tostring); @@ -7797,6 +8217,7 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) if (agtv_value->type == AGTV_NULL) { + pfree_agtype_value(agtv_value); return NULL; } else if (agtv_value->type == AGTV_INTEGER) @@ -7827,9 +8248,12 @@ static agtype_value *tostring_helper(Datum arg, Oid type, char *msghdr) } else { + int value_type = agtv_value->type; + + pfree_agtype_value(agtv_value); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s unsupported argument agtype %d", - msghdr, agtv_value->type))); + msghdr, value_type))); } } /* it is an unknown type */ @@ -7858,7 +8282,6 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; agtype_in_state agis_result; - agtype_value *elem; agtype_value string_elem; int count; int i; @@ -7896,18 +8319,22 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) for (i = 0; i < count; i++) { /* TODO: check element's type, it's value, and convert it to string if possible. */ + agtype_value elem; + bool elem_needs_free = false; enum agtype_value_type elem_type; - elem = get_ith_agtype_value_from_container(&agt_arg->root, i); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg->root, i, + &elem, + &elem_needs_free); string_elem.type = AGTV_STRING; - elem_type = elem ? elem->type : AGTV_NULL; + elem_type = elem.type; switch (elem_type) { case AGTV_STRING: - string_elem.val.string.val = elem->val.string.val; - string_elem.val.string.len = elem->val.string.len; + string_elem.val.string.val = elem.val.string.val; + string_elem.val.string.len = elem.val.string.len; agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, &string_elem); @@ -7918,7 +8345,7 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { int len; - string_elem.val.string.val = float8out_internal(elem->val.float_value); + string_elem.val.string.val = float8out_internal(elem.val.float_value); len = strlen(string_elem.val.string.val); string_elem.val.string.len = len; @@ -7932,7 +8359,7 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) { int len; - len = pg_lltoa(elem->val.int_value, buffer); + len = pg_lltoa(elem.val.int_value, buffer); string_elem.val.string.val = pnstrdup(buffer, len); string_elem.val.string.len = len; @@ -7950,6 +8377,8 @@ Datum age_tostringlist(PG_FUNCTION_ARGS) break; } + + free_agtype_value_no_copy(&elem, elem_needs_free); } agis_result.res = push_agtype_value(&agis_result.parse_state, @@ -8038,7 +8467,9 @@ Datum age_reverse(PG_FUNCTION_ARGS) else { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; + agtype_value agtv_value; + agtype_value *agtv_result = NULL; + bool value_needs_free = false; agtype_in_state result; agtype_parse_state *parse_state = NULL; agtype_value elem = {0}; @@ -8053,32 +8484,36 @@ Datum age_reverse(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) + if (agtv_value.type == AGTV_STRING) { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); + text_string = cstring_to_text_with_len(agtv_value.val.string.val, + agtv_value.val.string.len); + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("reverse() unsupported argument agtype %d", - agtv_value->type))); + agtv_value.type))); } } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { - agtv_value = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, NULL); + agtv_result = push_agtype_value(&parse_state, WAGT_BEGIN_ARRAY, NULL); while ((it = get_next_list_element(it, &agt_arg->root, &elem))) { - agtv_value = push_agtype_value(&parse_state, WAGT_ELEM, &elem); + agtv_result = push_agtype_value(&parse_state, WAGT_ELEM, &elem); } /* now reverse the list */ @@ -8095,12 +8530,12 @@ Datum age_reverse(PG_FUNCTION_ARGS) elems = NULL; - agtv_value = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); + agtv_result = push_agtype_value(&parse_state, WAGT_END_ARRAY, NULL); - Assert(agtv_value != NULL); - Assert(agtv_value->type == AGTV_ARRAY); + Assert(agtv_result != NULL); + Assert(agtv_result->type == AGTV_ARRAY); - PG_RETURN_POINTER(agtype_value_to_agtype(agtv_value)); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } else if (AGT_ROOT_IS_VPC(agt_arg)) @@ -8152,6 +8587,9 @@ Datum age_toupper(PG_FUNCTION_ARGS) int string_len; Oid type; int i; + agtype_value borrowed_value; + bool borrowed_needs_free = false; + bool borrowed_value_valid = false; if (!get_single_variadic_arg(fcinfo, "toUpper()", true, &arg, &type)) { @@ -8181,7 +8619,6 @@ Datum age_toupper(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -8190,20 +8627,28 @@ Datum age_toupper(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &borrowed_value, + &borrowed_needs_free); + borrowed_value_valid = true; /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (borrowed_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) + } + if (borrowed_value.type == AGTV_STRING) { - string = agtv_value->val.string.val; - string_len = agtv_value->val.string.len; + string = borrowed_value.val.string.val; + string_len = borrowed_value.val.string.len; } else + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toUpper() unsupported argument agtype %d", - agtv_value->type))); + borrowed_value.type))); + } } /* allocate the new string */ @@ -8218,6 +8663,9 @@ Datum age_toupper(PG_FUNCTION_ARGS) agtv_result.val.string.val = result; agtv_result.val.string.len = string_len; + if (borrowed_value_valid) + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } @@ -8232,6 +8680,9 @@ Datum age_tolower(PG_FUNCTION_ARGS) int string_len; Oid type; int i; + agtype_value borrowed_value; + bool borrowed_needs_free = false; + bool borrowed_value_valid = false; if (!get_single_variadic_arg(fcinfo, "toLower()", true, &arg, &type)) { @@ -8261,7 +8712,6 @@ Datum age_tolower(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -8270,20 +8720,28 @@ Datum age_tolower(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() only supports scalar arguments"))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &borrowed_value, + &borrowed_needs_free); + borrowed_value_valid = true; /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (borrowed_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) + } + if (borrowed_value.type == AGTV_STRING) { - string = agtv_value->val.string.val; - string_len = agtv_value->val.string.len; + string = borrowed_value.val.string.val; + string_len = borrowed_value.val.string.len; } else + { + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("toLower() unsupported argument agtype %d", - agtv_value->type))); + borrowed_value.type))); + } } /* allocate the new string */ @@ -8298,6 +8756,9 @@ Datum age_tolower(PG_FUNCTION_ARGS) agtv_result.val.string.val = result; agtv_result.val.string.len = string_len; + if (borrowed_value_valid) + free_agtype_value_no_copy(&borrowed_value, borrowed_needs_free); + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } @@ -8329,27 +8790,15 @@ Datum age_rtrim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "rTrim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("rTrim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8390,27 +8839,15 @@ Datum age_ltrim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "lTrim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("lTrim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8451,27 +8888,15 @@ Datum age_trim(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "trim()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("trim() unsupported argument agtype %d", - agtv_value->type))); } /* @@ -8544,35 +8969,17 @@ Datum age_right(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "right()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() unsupported argument agtype %d", - agtv_value->type))); - } } /* right() only supports integer and agtype integer for the second parameter. */ @@ -8602,28 +9009,10 @@ Datum age_right(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("right() unsupported argument agtype %d", - agtv_value->type))); - } - - string_len = agtv_value->val.int_value; + string_len = get_agtype_scalar_integer_no_copy(agt_arg, "right()"); } /* out of range and negative values are not supported in the opencypher spec */ @@ -8710,35 +9099,17 @@ Datum age_left(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "left()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() unsupported argument agtype %d", - agtv_value->type))); - } } /* left() only supports integer and agtype integer for the second parameter. */ @@ -8768,28 +9139,10 @@ Datum age_left(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("left() unsupported argument agtype %d", - agtv_value->type))); - } - - string_len = agtv_value->val.int_value; + string_len = get_agtype_scalar_integer_no_copy(agt_arg, "left()"); } /* out of range and negative values are not supported in the opencypher spec */ @@ -8890,35 +9243,17 @@ Datum age_substring(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() only supports scalar arguments"))); - } - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + text_string = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "substring()", + &is_null); + if (is_null) { PG_RETURN_NULL(); } - if (agtv_value->type == AGTV_STRING) - { - text_string = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - } - else - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() unsupported argument agtype %d", - agtv_value->type))); - } } /* @@ -8954,27 +9289,10 @@ Datum age_substring(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() only supports scalar arguments"))); - } - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* no need to check for agtype null because it is an error if found */ - if (agtv_value->type != AGTV_INTEGER) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("substring() unsupported argument agtype %d", - agtv_value->type))); - } - - param = agtv_value->val.int_value; + param = get_agtype_scalar_integer_no_copy(agt_arg, "substring()"); } /* out of range values are not supported in the opencypher spec */ @@ -9083,27 +9401,15 @@ Datum age_split(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("split() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + param = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "split()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - param = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("split() unsupported argument agtype %d", - agtv_value->type))); } if (i == 0) text_string = param; @@ -9226,27 +9532,15 @@ Datum age_replace(PG_FUNCTION_ARGS) else { agtype *agt_arg; - agtype_value *agtv_value; + bool is_null; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); - - if (!AGT_ROOT_IS_SCALAR(agt_arg)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("replace() only supports scalar arguments"))); - - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - - /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + param = get_agtype_scalar_string_as_text_no_copy(agt_arg, + "replace()", + &is_null); + if (is_null) PG_RETURN_NULL(); - if (agtv_value->type == AGTV_STRING) - param = cstring_to_text_with_len(agtv_value->val.string.val, - agtv_value->val.string.len); - else - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("replace() unsupported argument agtype %d", - agtv_value->type))); } if (i == 0) text_string = param; @@ -9306,7 +9600,8 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -9316,26 +9611,35 @@ static float8 get_float_compatible_arg(Datum arg, Oid type, char *funcname, errmsg("%s() only supports scalar arguments", funcname))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); return 0; + } - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - result = (float8) agtv_value->val.int_value; + result = (float8) agtv_value.val.int_value; } - else if (agtv_value->type == AGTV_FLOAT) - result = agtv_value->val.float_value; - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_FLOAT) + result = agtv_value.val.float_value; + else if (agtv_value.type == AGTV_NUMERIC) result = DatumGetFloat8(DirectFunctionCall1( numeric_float8_no_overflow, - NumericGetDatum(agtv_value->val.numeric))); + NumericGetDatum(agtv_value.val.numeric))); else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument agtype %d", - funcname, agtv_value->type))); + funcname, agtv_value.type))); + } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* there is a valid non null value */ @@ -9386,7 +9690,8 @@ static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, else { agtype *agt_arg; - agtype_value *agtv_value; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(arg); @@ -9396,36 +9701,45 @@ static Numeric get_numeric_compatible_arg(Datum arg, Oid type, char *funcname, errmsg("%s() only supports scalar arguments", funcname))); - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); /* check for agtype null */ - if (agtv_value->type == AGTV_NULL) + if (agtv_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); return 0; + } - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { result = DatumGetNumeric(DirectFunctionCall1( - int8_numeric, Int64GetDatum(agtv_value->val.int_value))); + int8_numeric, Int64GetDatum(agtv_value.val.int_value))); if (ag_type != NULL) *ag_type = AGTV_INTEGER; } - else if (agtv_value->type == AGTV_FLOAT) + else if (agtv_value.type == AGTV_FLOAT) { result = DatumGetNumeric(DirectFunctionCall1( - float8_numeric, Float8GetDatum(agtv_value->val.float_value))); + float8_numeric, Float8GetDatum(agtv_value.val.float_value))); if (ag_type != NULL) *ag_type = AGTV_FLOAT; } - else if (agtv_value->type == AGTV_NUMERIC) + else if (agtv_value.type == AGTV_NUMERIC) { - result = agtv_value->val.numeric; + result = agtv_value.val.numeric; if (ag_type != NULL) *ag_type = AGTV_NUMERIC; } else + { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument agtype %d", - funcname, agtv_value->type))); + funcname, agtv_value.type))); + } + + free_agtype_value_no_copy(&agtv_value, value_needs_free); } /* there is a valid non null value */ @@ -11022,9 +11336,12 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) { agtype *agt_arg0 = AG_GET_ARG_AGTYPE_P(0); agtype *agt_arg1 = AG_GET_ARG_AGTYPE_P(1); - agtype_value *agtv_lhs; - agtype_value *agtv_rhs; + agtype_value agtv_lhs; + agtype_value agtv_rhs; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; + agtype *result; /* get our args */ agt_arg0 = AG_GET_ARG_AGTYPE_P(0); @@ -11037,29 +11354,41 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) errmsg("arguments must resolve to a scalar"))); /* get the values */ - agtv_lhs = get_ith_agtype_value_from_container(&agt_arg0->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&agt_arg1->root, 0); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg0->root, 0, + &agtv_lhs, + &lhs_needs_free); + (void)get_ith_agtype_value_from_container_no_copy(&agt_arg1->root, 0, + &agtv_rhs, + &rhs_needs_free); /* only numbers are allowed */ - if ((agtv_lhs->type != AGTV_INTEGER && agtv_lhs->type != AGTV_FLOAT && - agtv_lhs->type != AGTV_NUMERIC) || (agtv_rhs->type != AGTV_INTEGER && - agtv_rhs->type != AGTV_FLOAT && agtv_rhs->type != AGTV_NUMERIC)) + if ((agtv_lhs.type != AGTV_INTEGER && agtv_lhs.type != AGTV_FLOAT && + agtv_lhs.type != AGTV_NUMERIC) || (agtv_rhs.type != AGTV_INTEGER && + agtv_rhs.type != AGTV_FLOAT && agtv_rhs.type != AGTV_NUMERIC)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("arguments must resolve to a number"))); /* check for agtype null */ - if (agtv_lhs->type == AGTV_NULL) + if (agtv_lhs.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); PG_RETURN_POINTER(agt_arg1); - if (agtv_rhs->type == AGTV_NULL) + } + if (agtv_rhs.type == AGTV_NULL) + { + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); PG_RETURN_POINTER(agt_arg0); + } /* we want to maintain the precision of the most precise input */ - if (agtv_lhs->type == AGTV_NUMERIC || agtv_rhs->type == AGTV_NUMERIC) + if (agtv_lhs.type == AGTV_NUMERIC || agtv_rhs.type == AGTV_NUMERIC) { agtv_result.type = AGTV_NUMERIC; } - else if (agtv_lhs->type == AGTV_FLOAT || agtv_rhs->type == AGTV_FLOAT) + else if (agtv_lhs.type == AGTV_FLOAT || agtv_rhs.type == AGTV_FLOAT) { agtv_result.type = AGTV_FLOAT; } @@ -11075,8 +11404,8 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) case AGTV_INTEGER: agtv_result.val.int_value = DatumGetInt64( DirectFunctionCall2(int8pl, - Int64GetDatum(agtv_lhs->val.int_value), - Int64GetDatum(agtv_rhs->val.int_value))); + Int64GetDatum(agtv_lhs.val.int_value), + Int64GetDatum(agtv_rhs.val.int_value))); break; /* for float it can be either, float + float or float + int */ case AGTV_FLOAT: @@ -11086,10 +11415,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) Datum dresult; /* extract and convert the values as necessary */ /* float + float */ - if (agtv_lhs->type == AGTV_FLOAT && agtv_rhs->type == AGTV_FLOAT) + if (agtv_lhs.type == AGTV_FLOAT && agtv_rhs.type == AGTV_FLOAT) { - dfl = Float8GetDatum(agtv_lhs->val.float_value); - dfr = Float8GetDatum(agtv_rhs->val.float_value); + dfl = Float8GetDatum(agtv_lhs.val.float_value); + dfr = Float8GetDatum(agtv_rhs.val.float_value); } /* float + int */ else @@ -11098,10 +11427,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) float8 fval; bool is_null; - ival = (agtv_lhs->type == AGTV_INTEGER) ? - agtv_lhs->val.int_value : agtv_rhs->val.int_value; - fval = (agtv_lhs->type == AGTV_FLOAT) ? - agtv_lhs->val.float_value : agtv_rhs->val.float_value; + ival = (agtv_lhs.type == AGTV_INTEGER) ? + agtv_lhs.val.int_value : agtv_rhs.val.int_value; + fval = (agtv_lhs.type == AGTV_FLOAT) ? + agtv_lhs.val.float_value : agtv_rhs.val.float_value; dfl = Float8GetDatum(get_float_compatible_arg(Int64GetDatum(ival), INT8OID, "", @@ -11124,21 +11453,21 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) Datum dresult; /* extract and convert the values as necessary */ /* numeric + numeric */ - if (agtv_lhs->type == AGTV_NUMERIC && agtv_rhs->type == AGTV_NUMERIC) + if (agtv_lhs.type == AGTV_NUMERIC && agtv_rhs.type == AGTV_NUMERIC) { - dnl = NumericGetDatum(agtv_lhs->val.numeric); - dnr = NumericGetDatum(agtv_rhs->val.numeric); + dnl = NumericGetDatum(agtv_lhs.val.numeric); + dnr = NumericGetDatum(agtv_rhs.val.numeric); } /* numeric + float */ - else if (agtv_lhs->type == AGTV_FLOAT || agtv_rhs->type == AGTV_FLOAT) + else if (agtv_lhs.type == AGTV_FLOAT || agtv_rhs.type == AGTV_FLOAT) { float8 fval; Numeric nval; - fval = (agtv_lhs->type == AGTV_FLOAT) ? - agtv_lhs->val.float_value : agtv_rhs->val.float_value; - nval = (agtv_lhs->type == AGTV_NUMERIC) ? - agtv_lhs->val.numeric : agtv_rhs->val.numeric; + fval = (agtv_lhs.type == AGTV_FLOAT) ? + agtv_lhs.val.float_value : agtv_rhs.val.float_value; + nval = (agtv_lhs.type == AGTV_NUMERIC) ? + agtv_lhs.val.numeric : agtv_rhs.val.numeric; dnl = DirectFunctionCall1(float8_numeric, Float8GetDatum(fval)); dnr = NumericGetDatum(nval); @@ -11149,10 +11478,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) int64 ival; Numeric nval; - ival = (agtv_lhs->type == AGTV_INTEGER) ? - agtv_lhs->val.int_value : agtv_rhs->val.int_value; - nval = (agtv_lhs->type == AGTV_NUMERIC) ? - agtv_lhs->val.numeric : agtv_rhs->val.numeric; + ival = (agtv_lhs.type == AGTV_INTEGER) ? + agtv_lhs.val.int_value : agtv_rhs.val.int_value; + nval = (agtv_lhs.type == AGTV_NUMERIC) ? + agtv_lhs.val.numeric : agtv_rhs.val.numeric; dnl = DirectFunctionCall1(int8_numeric, Int64GetDatum(ival)); dnr = NumericGetDatum(nval); @@ -11168,7 +11497,10 @@ Datum age_agtype_sum(PG_FUNCTION_ARGS) break; } /* return the result */ - PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + result = agtype_value_to_agtype(&agtv_result); + free_agtype_value_no_copy(&agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(&agtv_rhs, rhs_needs_free); + PG_RETURN_POINTER(result); } /* @@ -11621,7 +11953,9 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) /* only add non null values */ if (!is_null) { - agtype_value *agtv_value = NULL; + agtype_value agtv_value; + bool value_needs_free = false; + bool is_agtype_null = false; /* we need to check for agtype null and skip it, if found */ if (type == AGTYPEOID) @@ -11634,12 +11968,15 @@ Datum age_collect_aggtransfn(PG_FUNCTION_ARGS) /* get the scalar value */ if (AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root)) { - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); + is_agtype_null = agtv_value.type == AGTV_NULL; + free_agtype_value_no_copy(&agtv_value, value_needs_free); } } /* skip the arg if agtype null */ - if (agtv_value == NULL || agtv_value->type != AGTV_NULL) + if (!is_agtype_null) { add_agtype(arg, is_null, castate, type, false); } @@ -11924,38 +12261,48 @@ Datum age_eq_tilde(PG_FUNCTION_ARGS) /* they both need to scalars */ if (AGT_ROOT_IS_SCALAR(agt_string) && AGT_ROOT_IS_SCALAR(agt_pattern)) { - agtype_value *agtv_string; - agtype_value *agtv_pattern; + agtype_value agtv_string; + agtype_value agtv_pattern; + bool string_needs_free = false; + bool pattern_needs_free = false; /* get the contents of each container */ - agtv_string = get_ith_agtype_value_from_container(&agt_string->root, 0); - agtv_pattern = get_ith_agtype_value_from_container(&agt_pattern->root, - 0); + get_scalar_agtype_value_no_copy(agt_string, &agtv_string, + &string_needs_free); + get_scalar_agtype_value_no_copy(agt_pattern, &agtv_pattern, + &pattern_needs_free); /* if either are agtype null, return NULL */ - if (agtv_string->type == AGTV_NULL || - agtv_pattern->type == AGTV_NULL) + if (agtv_string.type == AGTV_NULL || + agtv_pattern.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); PG_RETURN_NULL(); } /* only strings can be compared, all others are errors */ - if (agtv_string->type == AGTV_STRING && - agtv_pattern->type == AGTV_STRING) + if (agtv_string.type == AGTV_STRING && + agtv_pattern.type == AGTV_STRING) { text *string = NULL; text *pattern = NULL; Datum result; - string = cstring_to_text_with_len(agtv_string->val.string.val, - agtv_string->val.string.len); - pattern = cstring_to_text_with_len(agtv_pattern->val.string.val, - agtv_pattern->val.string.len); + string = cstring_to_text_with_len(agtv_string.val.string.val, + agtv_string.val.string.len); + pattern = cstring_to_text_with_len(agtv_pattern.val.string.val, + agtv_pattern.val.string.len); result = (DirectFunctionCall2Coll(textregexeq, C_COLLATION_OID, PointerGetDatum(string), PointerGetDatum(pattern))); + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); return boolean_to_agtype(DatumGetBool(result)); } + + free_agtype_value_no_copy(&agtv_string, string_needs_free); + free_agtype_value_no_copy(&agtv_pattern, pattern_needs_free); } /* if we got here we have values that are invalid */ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -12031,6 +12378,7 @@ Datum age_keys(PG_FUNCTION_ARGS) agtype_value obj_key = {0}; agtype_iterator *it = NULL; agtype_parse_state *parse_state = NULL; + bool result_needs_free = false; /* check for null */ if (PG_ARGISNULL(0)) @@ -12047,11 +12395,18 @@ Datum age_keys(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_SCALAR(agt_arg)) { - agtv_result = get_ith_agtype_value_from_container(&agt_arg->root, 0); + agtype_value scalar_result; + + get_scalar_agtype_value_no_copy(agt_arg, &scalar_result, + &result_needs_free); + agtv_result = &scalar_result; /* is it an agtype null, return null if it is */ if (agtv_result->type == AGTV_NULL) + { + free_agtype_value_no_copy(agtv_result, result_needs_free); PG_RETURN_NULL(); + } /* check for proper agtype and extract the properties field */ if (agtv_result->type == AGTV_EDGE || @@ -12071,11 +12426,13 @@ Datum age_keys(PG_FUNCTION_ARGS) } else { + free_agtype_value_no_copy(agtv_result, result_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("keys() argument must be a vertex, edge, object or null"))); } agt_arg = agtype_value_to_agtype(agtv_result); + free_agtype_value_no_copy(&scalar_result, result_needs_free); agtv_result = NULL; } else if (!AGT_ROOT_IS_OBJECT(agt_arg)) @@ -12109,8 +12466,10 @@ PG_FUNCTION_INFO_V1(age_nodes); Datum age_nodes(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_in_state agis_result; + agtype *result; + bool path_needs_free = false; int i = 0; /* check for null */ @@ -12128,19 +12487,22 @@ Datum age_nodes(PG_FUNCTION_ARGS) errmsg("nodes() argument must resolve to a scalar value"))); } - /* get the potential path out of the array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* is it an agtype null? */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) { - PG_RETURN_NULL(); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + PG_RETURN_NULL(); } /* verify that it is an agtype path */ - if (agtv_path->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("nodes() argument must be a path"))); + } /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); @@ -12149,10 +12511,10 @@ Datum age_nodes(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* push in each vertex (every other entry) from the path */ - for (i = 0; i < agtv_path->val.array.num_elems; i += 2) + for (i = 0; i < agtv_path.val.array.num_elems; i += 2) { agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[i]); + &agtv_path.val.array.elems[i]); } /* push the end of the array */ @@ -12160,7 +12522,10 @@ Datum age_nodes(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_labels); @@ -12175,9 +12540,11 @@ PG_FUNCTION_INFO_V1(age_labels); Datum age_labels(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_temp = NULL; + agtype_value agtv_temp; agtype_value *agtv_label = NULL; agtype_in_state agis_result; + agtype *result; + bool temp_needs_free = false; /* get the vertex argument */ agt_arg = AG_GET_ARG_AGTYPE_P(0); @@ -12190,26 +12557,26 @@ Datum age_labels(PG_FUNCTION_ARGS) errmsg("labels() argument must resolve to a scalar value"))); } + get_scalar_agtype_value_no_copy(agt_arg, &agtv_temp, &temp_needs_free); + /* is it an agtype null? */ - if (AGTYPE_CONTAINER_IS_SCALAR(&agt_arg->root) && - AGTE_IS_NULL((&agt_arg->root)->children[0])) + if (agtv_temp.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); PG_RETURN_NULL(); } - /* get the potential vertex */ - agtv_temp = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* verify that it is an agtype vertex */ - if (agtv_temp->type != AGTV_VERTEX) + if (agtv_temp.type != AGTV_VERTEX) { + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("labels() argument must be a vertex"))); } /* get the label from the vertex */ - agtv_label = AGTYPE_VERTEX_GET_LABEL(agtv_temp); + agtv_label = AGTYPE_VERTEX_GET_LABEL(&agtv_temp); /* it cannot be NULL */ Assert(agtv_label != NULL); @@ -12229,7 +12596,10 @@ Datum age_labels(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_temp, temp_needs_free); + + PG_RETURN_POINTER(result); } PG_FUNCTION_INFO_V1(age_relationships); @@ -12239,8 +12609,10 @@ PG_FUNCTION_INFO_V1(age_relationships); Datum age_relationships(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_path = NULL; + agtype_value agtv_path; agtype_in_state agis_result; + agtype *result; + bool path_needs_free = false; int i = 0; /* check for null */ @@ -12258,19 +12630,22 @@ Datum age_relationships(PG_FUNCTION_ARGS) errmsg("relationships() argument must resolve to a scalar value"))); } - /* get the potential path out of the array */ - agtv_path = get_ith_agtype_value_from_container(&agt_arg->root, 0); + get_scalar_agtype_value_no_copy(agt_arg, &agtv_path, &path_needs_free); /* is it an agtype null? */ - if (agtv_path->type == AGTV_NULL) + if (agtv_path.type == AGTV_NULL) { - PG_RETURN_NULL(); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + PG_RETURN_NULL(); } /* verify that it is an agtype path */ - if (agtv_path->type != AGTV_PATH) + if (agtv_path.type != AGTV_PATH) + { + free_agtype_value_no_copy(&agtv_path, path_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("relationships() argument must be a path"))); + } /* clear the result structure */ MemSet(&agis_result, 0, sizeof(agtype_in_state)); @@ -12279,10 +12654,10 @@ Datum age_relationships(PG_FUNCTION_ARGS) agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* push in each edge (every other entry) from the path */ - for (i = 1; i < agtv_path->val.array.num_elems; i += 2) + for (i = 1; i < agtv_path.val.array.num_elems; i += 2) { agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[i]); + &agtv_path.val.array.elems[i]); } /* push the end of the array */ @@ -12290,7 +12665,10 @@ Datum age_relationships(PG_FUNCTION_ARGS) WAGT_END_ARRAY, NULL); /* convert the agtype_value to a datum to return to the caller */ - PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + result = agtype_value_to_agtype(agis_result.res); + free_agtype_value_no_copy(&agtv_path, path_needs_free); + + PG_RETURN_POINTER(result); } /* @@ -12320,8 +12698,8 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, else if (type == AGTYPEOID) { agtype *agt_arg = NULL; - agtype_value *agtv_value = NULL; - agtype_container *agtc = NULL; + agtype_value agtv_value; + bool value_needs_free = false; /* get the agtype argument */ agt_arg = DATUM_GET_AGTYPE_P(d); @@ -12332,24 +12710,27 @@ static int64 get_int64_from_int_datums(Datum d, Oid type, char *funcname, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() only supports scalar arguments", funcname))); } - /* check for agtype null*/ - agtc = &agt_arg->root; - if (AGTE_IS_NULL(agtc->children[0])) + /* extract it from the scalar array */ + get_scalar_agtype_value_no_copy(agt_arg, &agtv_value, + &value_needs_free); + + /* check for agtype null */ + if (agtv_value.type == AGTV_NULL) { + free_agtype_value_no_copy(&agtv_value, value_needs_free); *is_agnull = true; return 0; } - /* extract it from the scalar array */ - agtv_value = get_ith_agtype_value_from_container(&agt_arg->root, 0); - /* check for agtype integer */ - if (agtv_value->type == AGTV_INTEGER) + if (agtv_value.type == AGTV_INTEGER) { - result = agtv_value->val.int_value; + result = agtv_value.val.int_value; + free_agtype_value_no_copy(&agtv_value, value_needs_free); } else { + free_agtype_value_no_copy(&agtv_value, value_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("%s() unsupported argument type", funcname))); diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index ea3f9e58e..1b786fc87 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -44,6 +44,9 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text); static agtype *delete_from_object(agtype *agt, char *keyptr, int keylen); static agtype *delete_from_array(agtype *agt, agtype* indexes); static bool parse_agtype_index_string(char *str, int len, long *lindex); +static void get_scalar_agtype_value_no_copy(agtype *agt, agtype_value *value, + bool *needs_free); +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen) @@ -164,6 +167,26 @@ static bool parse_agtype_index_string(char *str, int len, long *lindex) return true; } +static void get_scalar_agtype_value_no_copy(agtype *agt, agtype_value *value, + bool *needs_free) +{ + bool found; + + Assert(AGT_ROOT_IS_SCALAR(agt)); + + found = get_ith_agtype_value_from_container_no_copy(&agt->root, 0, value, + needs_free); + Assert(found); +} + +static void free_agtype_value_no_copy(agtype_value *value, bool needs_free) +{ + if (needs_free) + { + pfree_agtype_value_content(value); + } +} + Datum get_numeric_datum_from_agtype_value(agtype_value *agtv) { switch (agtv->type) @@ -206,7 +229,11 @@ Datum agtype_add(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; /* If both are not scalars */ if (!(AGT_ROOT_IS_SCALAR(lhs) && AGT_ROOT_IS_SCALAR(rhs))) @@ -217,12 +244,16 @@ Datum agtype_add(PG_FUNCTION_ARGS) } /* Both are scalar */ - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -288,6 +319,8 @@ Datum agtype_add(PG_FUNCTION_ARGS) { Datum agt = AGTYPE_P_GET_DATUM(agtype_concat_impl(lhs, rhs)); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_DATUM(agt); } else @@ -298,6 +331,8 @@ Datum agtype_add(PG_FUNCTION_ARGS) errmsg("Invalid input parameter types for agtype_add"))); } + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -476,7 +511,11 @@ Datum agtype_sub(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; /* * Logic to handle when the rhs is a non scalar array. In this @@ -552,43 +591,65 @@ Datum agtype_sub(PG_FUNCTION_ARGS) */ if(!AGT_ROOT_IS_SCALAR(lhs)) { - agtype_value *key; - key = get_ith_agtype_value_from_container(&rhs->root, 0); + agtype_value key_value; + agtype_value *key = &key_value; + bool key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy(&rhs->root, 0, key, + &key_needs_free); if (AGT_ROOT_IS_OBJECT(lhs) && key->type == AGTV_STRING) { - AG_RETURN_AGTYPE_P(delete_from_object(lhs, key->val.string.val, - key->val.string.len)); + agtype *result = delete_from_object(lhs, key->val.string.val, + key->val.string.len); + + free_agtype_value_no_copy(key, key_needs_free); + AG_RETURN_AGTYPE_P(result); } else if (AGT_ROOT_IS_ARRAY(lhs) && key->type == AGTV_INTEGER) { - AG_RETURN_AGTYPE_P(delete_from_array(lhs, rhs)); + agtype *result = delete_from_array(lhs, rhs); + + free_agtype_value_no_copy(key, key_needs_free); + AG_RETURN_AGTYPE_P(result); } else { if (AGT_ROOT_IS_OBJECT(lhs)) { + char *type_name = agtype_value_type_to_string(key->type); + + free_agtype_value_no_copy(key, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype string, not agtype %s", - agtype_value_type_to_string(key->type)))); + type_name))); } else if (AGT_ROOT_IS_ARRAY(lhs)) { + char *type_name = agtype_value_type_to_string(key->type); + + free_agtype_value_no_copy(key, key_needs_free); ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("expected agtype integer, not agtype %s", - agtype_value_type_to_string(key->type)))); + type_name))); } + + free_agtype_value_no_copy(key, key_needs_free); } } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -635,6 +696,8 @@ Datum agtype_sub(PG_FUNCTION_ARGS) errmsg("Invalid input parameter types for agtype_sub"))); } + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -670,7 +733,9 @@ Datum agtype_neg(PG_FUNCTION_ARGS) { agtype *v = AG_GET_ARG_AGTYPE_P(0); agtype_value *agtv_value; + agtype_value agtv_value_value; agtype_value agtv_result; + bool value_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(v))) { @@ -680,11 +745,13 @@ Datum agtype_neg(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_value = get_ith_agtype_value_from_container(&v->root, 0); + get_scalar_agtype_value_no_copy(v, &agtv_value_value, &value_needs_free); + agtv_value = &agtv_value_value; /* openCypher: arithmetic over null yields null. */ if (agtv_value->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_value, value_needs_free); PG_RETURN_NULL(); } @@ -712,6 +779,7 @@ Datum agtype_neg(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter type for agtype_neg"))); + free_agtype_value_no_copy(agtv_value, value_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -726,7 +794,11 @@ Datum agtype_mul(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -736,12 +808,16 @@ Datum agtype_mul(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -785,6 +861,8 @@ Datum agtype_mul(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mul"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -822,7 +900,11 @@ Datum agtype_div(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -832,12 +914,16 @@ Datum agtype_div(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -909,7 +995,9 @@ Datum agtype_div(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_div"))); - AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); + AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } PG_FUNCTION_INFO_V1(agtype_any_div); @@ -946,7 +1034,11 @@ Datum agtype_mod(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -956,12 +1048,16 @@ Datum agtype_mod(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -1005,6 +1101,8 @@ Datum agtype_mod(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_mod"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -1042,7 +1140,11 @@ Datum agtype_pow(PG_FUNCTION_ARGS) agtype *rhs = AG_GET_ARG_AGTYPE_P(1); agtype_value *agtv_lhs; agtype_value *agtv_rhs; + agtype_value agtv_lhs_value; + agtype_value agtv_rhs_value; agtype_value agtv_result; + bool lhs_needs_free = false; + bool rhs_needs_free = false; if (!(AGT_ROOT_IS_SCALAR(lhs)) || !(AGT_ROOT_IS_SCALAR(rhs))) { @@ -1052,12 +1154,16 @@ Datum agtype_pow(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } - agtv_lhs = get_ith_agtype_value_from_container(&lhs->root, 0); - agtv_rhs = get_ith_agtype_value_from_container(&rhs->root, 0); + get_scalar_agtype_value_no_copy(lhs, &agtv_lhs_value, &lhs_needs_free); + get_scalar_agtype_value_no_copy(rhs, &agtv_rhs_value, &rhs_needs_free); + agtv_lhs = &agtv_lhs_value; + agtv_rhs = &agtv_rhs_value; /* openCypher: arithmetic over null yields null. */ if (agtv_lhs->type == AGTV_NULL || agtv_rhs->type == AGTV_NULL) { + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); PG_RETURN_NULL(); } @@ -1101,6 +1207,8 @@ Datum agtype_pow(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid input parameter types for agtype_pow"))); + free_agtype_value_no_copy(agtv_lhs, lhs_needs_free); + free_agtype_value_no_copy(agtv_rhs, rhs_needs_free); AG_RETURN_AGTYPE_P(agtype_value_to_agtype(&agtv_result)); } @@ -1372,8 +1480,10 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); agtype *key = AG_GET_ARG_AGTYPE_P(1); - agtype_value *aval; + agtype_value aval_value; + agtype_value *aval = &aval_value; agtype_value *v = NULL; + bool aval_needs_free = false; if (AGT_ROOT_IS_SCALAR(agt)) { @@ -1382,7 +1492,7 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) if (AGT_ROOT_IS_SCALAR(key)) { - aval = get_ith_agtype_value_from_container(&key->root, 0); + get_scalar_agtype_value_no_copy(key, aval, &aval_needs_free); } else { @@ -1404,6 +1514,8 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) aval); } + free_agtype_value_no_copy(aval, aval_needs_free); + PG_RETURN_BOOL(v != NULL); } @@ -2103,6 +2215,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) int i; bool have_object = false, have_array = false; agtype_value *agtvp = NULL; + agtype_value scalar_value; + bool scalar_needs_free = false; agtype_value tv; agtype_container *container; @@ -2136,7 +2250,10 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) /* Extract the scalar value */ if (npath <= 0) { - agtvp = get_ith_agtype_value_from_container(container, 0); + (void)get_ith_agtype_value_from_container_no_copy(container, 0, + &scalar_value, + &scalar_needs_free); + agtvp = &scalar_value; } } @@ -2163,14 +2280,18 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) for (i = 0; i < npath; i++) { - agtype_value *cur_key = - get_ith_agtype_value_from_container(&path->root, i); + agtype_value cur_key; + bool cur_key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy(&path->root, i, + &cur_key, + &cur_key_needs_free); - if (have_object && cur_key->type == AGTV_STRING) + if (have_object && cur_key.type == AGTV_STRING) { agtvp = find_agtype_value_from_container(container, - AGT_FOBJECT, - cur_key); + AGT_FOBJECT, + &cur_key); } else if (have_array) { @@ -2181,30 +2302,33 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) * for array on LHS, there should be an integer or a * valid integer string on RHS */ - if (cur_key->type == AGTV_INTEGER) + if (cur_key.type == AGTV_INTEGER) { - lindex = cur_key->val.int_value; + lindex = cur_key.val.int_value; } - else if (cur_key->type == AGTV_STRING) + else if (cur_key.type == AGTV_STRING) { /* * extract the integer from the string, * if character other than a digit is found, return null */ - if (!parse_agtype_index_string(cur_key->val.string.val, - cur_key->val.string.len, + if (!parse_agtype_index_string(cur_key.val.string.val, + cur_key.val.string.len, &lindex)) { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } } else { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } if (lindex > INT_MAX || lindex < INT_MIN) { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2227,6 +2351,7 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) if (-lindex > nelements) { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } else @@ -2235,19 +2360,33 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) } } - agtvp = get_ith_agtype_value_from_container(container, index); + if (get_ith_agtype_value_from_container_no_copy(container, index, + &scalar_value, + &scalar_needs_free)) + { + agtvp = &scalar_value; + } + else + { + agtvp = NULL; + } } else { + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } if (agtvp == NULL) { + free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } else if (i == npath - 1) { + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); break; } @@ -2268,6 +2407,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) have_object = agtvp->type == AGTV_OBJECT; have_array = agtvp->type == AGTV_ARRAY; } + + free_agtype_value_no_copy(&cur_key, cur_key_needs_free); } if (as_text) @@ -2275,17 +2416,22 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) /* special-case output for string and null values */ if (agtvp->type == AGTV_STRING) { - PG_RETURN_TEXT_P(cstring_to_text_with_len(agtvp->val.string.val, - agtvp->val.string.len)); + text *result = cstring_to_text_with_len(agtvp->val.string.val, + agtvp->val.string.len); + + free_agtype_value_no_copy(agtvp, scalar_needs_free); + PG_RETURN_TEXT_P(result); } if (agtvp->type == AGTV_NULL) { + free_agtype_value_no_copy(agtvp, scalar_needs_free); PG_RETURN_NULL(); } } res = agtype_value_to_agtype(agtvp); + free_agtype_value_no_copy(agtvp, scalar_needs_free); if (as_text) { diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c index 8cb639978..484cef014 100644 --- a/src/backend/utils/adt/agtype_util.c +++ b/src/backend/utils/adt/agtype_util.c @@ -621,6 +621,43 @@ agtype_value *get_ith_agtype_value_from_container(agtype_container *container, return result; } +/* + * Get i-th value of an agtype array without copying the value contents when + * that is safe. Strings, numerics, and nested containers point into the + * original container. Extended composite values still allocate while + * deserializing, and needs_free tells the caller when the returned value + * content must be released with pfree_agtype_value_content(). + */ +bool get_ith_agtype_value_from_container_no_copy(agtype_container *container, + uint32 i, + agtype_value *result, + bool *needs_free) +{ + char *base_addr; + uint32 nelements; + + if (!AGTYPE_CONTAINER_IS_ARRAY(container)) + ereport(ERROR, (errmsg("container is not an agtype array"))); + + nelements = AGTYPE_CONTAINER_SIZE(container); + + if (i >= nelements) + return false; + + base_addr = (char *)&container->children[nelements]; + fill_agtype_value_no_copy(container, i, base_addr, + get_agtype_offset(container, i), result); + + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + + return true; +} + /* * Get type of i-th value of an agtype array. */ diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index f89e0d816..66cf37b17 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -569,6 +569,10 @@ agtype_value *find_agtype_value_from_container(agtype_container *container, agtype_value *key); agtype_value *get_ith_agtype_value_from_container(agtype_container *container, uint32 i); +bool get_ith_agtype_value_from_container_no_copy(agtype_container *container, + uint32 i, + agtype_value *result, + bool *needs_free); enum agtype_value_type get_ith_agtype_value_type(agtype_container *container, uint32 i); agtype_value *push_agtype_value(agtype_parse_state **pstate, From 14e8a4f30ce47f6fabb32b7c0ca8c97e2972ae6b Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:39 +0900 Subject: [PATCH 12/15] Consolidate Cypher DML setup Combine the custom-plan, custom-path, path-replacement, clause-function, and external-cast cleanup work for Cypher DML. The grouped change removes duplicated setup between DML nodes, batches clause function OID loading, and skips redundant external function casts. Keeping these together makes the planner and executor contract for write clauses easier to audit. --- src/backend/optimizer/cypher_createplan.c | 142 +------ src/backend/optimizer/cypher_pathnode.c | 67 +-- src/backend/optimizer/cypher_paths.c | 147 +------ src/backend/parser/cypher_expr.c | 5 + src/backend/utils/adt/agtype.c | 195 ++++++++- src/backend/utils/adt/agtype_ops.c | 486 +++++++++++++++++----- 6 files changed, 624 insertions(+), 418 deletions(-) diff --git a/src/backend/optimizer/cypher_createplan.c b/src/backend/optimizer/cypher_createplan.c index df9495a32..d7434dece 100644 --- a/src/backend/optimizer/cypher_createplan.c +++ b/src/backend/optimizer/cypher_createplan.c @@ -54,9 +54,9 @@ static void apply_custom_plan_metadata(CustomScan *cs, CustomPath *best_path) cs->scan.plan.parallel_safe = best_path->path.parallel_safe; } -Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, - CustomPath *best_path, List *tlist, - List *clauses, List *custom_plans) +static CustomScan *make_cypher_dml_plan(CustomPath *best_path, List *tlist, + List *custom_plans, + const CustomScanMethods *methods) { CustomScan *cs; Plan *subplan = linitial(custom_plans); @@ -85,45 +85,25 @@ Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, cs->custom_private = best_path->custom_private; cs->custom_scan_tlist = subplan->targetlist; cs->custom_relids = NULL; - cs->methods = &cypher_create_plan_methods; + cs->methods = methods; - return (Plan *)cs; + return cs; +} + +Plan *plan_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, + CustomPath *best_path, List *tlist, + List *clauses, List *custom_plans) +{ + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_create_plan_methods); } Plan *plan_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - apply_custom_plan_metadata(cs, best_path); - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - cs->custom_private = best_path->custom_private; - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_set_plan_methods; - - return (Plan *)cs; + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_set_plan_methods); } /* @@ -134,50 +114,8 @@ Plan *plan_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - apply_custom_plan_metadata(cs, best_path); - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ - /* - * the scan list of the delete node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - /* child plan nodes are here, Postgres processed them for us. */ - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - /* transfer delete metadata needed by the DELETE clause. */ - cs->custom_private = best_path->custom_private; - /* - * the scan list of the delete node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_delete_plan_methods; - - return (Plan *)cs; + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_delete_plan_methods); } /* @@ -188,48 +126,6 @@ Plan *plan_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, List *tlist, List *clauses, List *custom_plans) { - CustomScan *cs; - Plan *subplan = linitial(custom_plans); - - cs = makeNode(CustomScan); - - apply_custom_plan_metadata(cs, best_path); - - cs->scan.plan.plan_node_id = 0; /* Set later in set_plan_refs */ - /* - * the scan list of the merge node, used for its ScanTupleSlot used - * by its parent in the execution phase. - */ - cs->scan.plan.targetlist = tlist; - cs->scan.plan.qual = NIL; - cs->scan.plan.lefttree = NULL; - cs->scan.plan.righttree = NULL; - cs->scan.plan.initPlan = NIL; - - cs->scan.plan.extParam = NULL; - cs->scan.plan.allParam = NULL; - - /* - * We do not want Postgres to assume we are scanning a table, postgres' - * optimizer will make assumptions about our targetlist that are false - */ - cs->scan.scanrelid = 0; - - cs->flags = best_path->flags; - - /* child plan nodes are here, Postgres processed them for us. */ - cs->custom_plans = custom_plans; - cs->custom_exprs = NIL; - /* transfer delete metadata needed by the MERGE clause. */ - cs->custom_private = best_path->custom_private; - /* - * the scan list of the merge node's children, used for ScanTupleSlot - * in execution. - */ - cs->custom_scan_tlist = subplan->targetlist; - - cs->custom_relids = NULL; - cs->methods = &cypher_merge_plan_methods; - - return (Plan *)cs; + return (Plan *)make_cypher_dml_plan(best_path, tlist, custom_plans, + &cypher_merge_plan_methods); } diff --git a/src/backend/optimizer/cypher_pathnode.c b/src/backend/optimizer/cypher_pathnode.c index 454cece32..664093aa5 100644 --- a/src/backend/optimizer/cypher_pathnode.c +++ b/src/backend/optimizer/cypher_pathnode.c @@ -33,6 +33,8 @@ static Const *convert_sublink_to_subplan(PlannerInfo *root, List *custom_private); static bool expr_has_sublink(Node *node, void *context); +static CustomPath *make_cypher_dml_path(RelOptInfo *rel, List *custom_private, + const CustomPathMethods *methods); static Path *select_best_child_path(RelOptInfo *rel); static void initialize_cypher_dml_path(CustomPath *cp, RelOptInfo *rel); static void apply_child_path_costs(CustomPath *cp, Path *best_child); @@ -49,31 +51,14 @@ const CustomPathMethods cypher_merge_path_methods = { CustomPath *create_cypher_create_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - initialize_cypher_dml_path(cp, rel); - cp->flags = 0; - cp->custom_private = custom_private; - cp->methods = &cypher_create_path_methods; - - return cp; + return make_cypher_dml_path(rel, custom_private, + &cypher_create_path_methods); } CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - initialize_cypher_dml_path(cp, rel); - cp->flags = 0; - cp->custom_private = custom_private; - cp->methods = &cypher_set_path_methods; - - return cp; + return make_cypher_dml_path(rel, custom_private, &cypher_set_path_methods); } /* @@ -83,18 +68,8 @@ CustomPath *create_cypher_set_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - initialize_cypher_dml_path(cp, rel); - cp->flags = 0; - /* Store the metadata Delete will need in the execution phase. */ - cp->custom_private = custom_private; - /* Tells Postgres how to turn this path to the correct CustomScan */ - cp->methods = &cypher_delete_path_methods; - - return cp; + return make_cypher_dml_path(rel, custom_private, + &cypher_delete_path_methods); } /* @@ -104,13 +79,6 @@ CustomPath *create_cypher_delete_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, List *custom_private) { - CustomPath *cp; - - cp = makeNode(CustomPath); - - initialize_cypher_dml_path(cp, rel); - cp->flags = 0; - /* * Store the metadata Merge will need in the execution phase. * We may have a sublink here in case the user used a list @@ -118,15 +86,22 @@ CustomPath *create_cypher_merge_path(PlannerInfo *root, RelOptInfo *rel, */ if (rel->subroot->parse->hasSubLinks) { - cp->custom_private = list_make1(convert_sublink_to_subplan(root, custom_private)); - } - else - { - cp->custom_private = custom_private; + custom_private = list_make1(convert_sublink_to_subplan(root, + custom_private)); } - /* Tells Postgres how to turn this path to the correct CustomScan */ - cp->methods = &cypher_merge_path_methods; + return make_cypher_dml_path(rel, custom_private, &cypher_merge_path_methods); +} + +static CustomPath *make_cypher_dml_path(RelOptInfo *rel, List *custom_private, + const CustomPathMethods *methods) +{ + CustomPath *cp = makeNode(CustomPath); + + initialize_cypher_dml_path(cp, rel); + cp->flags = 0; + cp->custom_private = custom_private; + cp->methods = methods; return cp; } diff --git a/src/backend/optimizer/cypher_paths.c b/src/backend/optimizer/cypher_paths.c index bf1335cbe..4bc4ca2b5 100644 --- a/src/backend/optimizer/cypher_paths.c +++ b/src/backend/optimizer/cypher_paths.c @@ -37,6 +37,9 @@ typedef enum cypher_clause_kind CYPHER_CLAUSE_MERGE } cypher_clause_kind; +typedef CustomPath *(*cypher_path_factory)(PlannerInfo *root, RelOptInfo *rel, + List *custom_private); + static set_rel_pathlist_hook_type prev_set_rel_pathlist_hook; static Oid cypher_create_clause_func_oid = InvalidOid; @@ -49,20 +52,12 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, RangeTblEntry *rte); static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte); static void register_cypher_clause_function_oid_callbacks(void); -static Oid get_cypher_create_clause_func_oid(void); -static Oid get_cypher_set_clause_func_oid(void); -static Oid get_cypher_delete_clause_func_oid(void); -static Oid get_cypher_merge_clause_func_oid(void); +static void load_cypher_clause_function_oids(void); static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, uint32 hash_value); -static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte); +static void replace_with_cypher_dml_path(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, + cypher_path_factory factory); void set_rel_pathlist_init(void) { @@ -84,16 +79,16 @@ static void set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, switch (get_cypher_clause_kind(rte)) { case CYPHER_CLAUSE_CREATE: - handle_cypher_create_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_create_path); break; case CYPHER_CLAUSE_SET: - handle_cypher_set_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_set_path); break; case CYPHER_CLAUSE_DELETE: - handle_cypher_delete_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_delete_path); break; case CYPHER_CLAUSE_MERGE: - handle_cypher_merge_clause(root, rel, rti, rte); + replace_with_cypher_dml_path(root, rel, rte, create_cypher_merge_path); break; case CYPHER_CLAUSE_NONE: break; @@ -129,13 +124,15 @@ static cypher_clause_kind get_cypher_clause_kind(RangeTblEntry *rte) fe = (FuncExpr *)te->expr; - if (fe->funcid == get_cypher_create_clause_func_oid()) + load_cypher_clause_function_oids(); + + if (fe->funcid == cypher_create_clause_func_oid) return CYPHER_CLAUSE_CREATE; - if (fe->funcid == get_cypher_set_clause_func_oid()) + if (fe->funcid == cypher_set_clause_func_oid) return CYPHER_CLAUSE_SET; - if (fe->funcid == get_cypher_delete_clause_func_oid()) + if (fe->funcid == cypher_delete_clause_func_oid) return CYPHER_CLAUSE_DELETE; - if (fe->funcid == get_cypher_merge_clause_func_oid()) + if (fe->funcid == cypher_merge_clause_func_oid) return CYPHER_CLAUSE_MERGE; else return CYPHER_CLAUSE_NONE; @@ -155,7 +152,7 @@ static void register_cypher_clause_function_oid_callbacks(void) } } -static Oid get_cypher_create_clause_func_oid(void) +static void load_cypher_clause_function_oids(void) { register_cypher_clause_function_oid_callbacks(); @@ -165,46 +162,23 @@ static Oid get_cypher_create_clause_func_oid(void) get_ag_func_oid(CREATE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - return cypher_create_clause_func_oid; -} - -static Oid get_cypher_set_clause_func_oid(void) -{ - register_cypher_clause_function_oid_callbacks(); - if (!OidIsValid(cypher_set_clause_func_oid)) { cypher_set_clause_func_oid = get_ag_func_oid(SET_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - return cypher_set_clause_func_oid; -} - -static Oid get_cypher_delete_clause_func_oid(void) -{ - register_cypher_clause_function_oid_callbacks(); - if (!OidIsValid(cypher_delete_clause_func_oid)) { cypher_delete_clause_func_oid = get_ag_func_oid(DELETE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - return cypher_delete_clause_func_oid; -} - -static Oid get_cypher_merge_clause_func_oid(void) -{ - register_cypher_clause_function_oid_callbacks(); - if (!OidIsValid(cypher_merge_clause_func_oid)) { cypher_merge_clause_func_oid = get_ag_func_oid(MERGE_CLAUSE_FUNCTION_NAME, 1, INTERNALOID); } - - return cypher_merge_clause_func_oid; } static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, @@ -216,86 +190,9 @@ static void invalidate_cypher_clause_function_oids(Datum arg, int cache_id, cypher_merge_clause_func_oid = InvalidOid; } -/* replace all possible paths with our CustomPath */ -static void handle_cypher_delete_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; - - cp = create_cypher_delete_path(root, rel, custom_private); - - /* Discard any preexisting paths */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); -} - -/* - * Take the paths possible for the RelOptInfo that represents our - * _cypher_delete_clause function replace them with our delete clause - * path. The original paths will be children to the new delete path. - */ -static void handle_cypher_create_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; - - cp = create_cypher_create_path(root, rel, custom_private); - - /* Discard any preexisting paths, they should be under the cp path */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - /* Add the new path to the rel. */ - add_path(rel, (Path *)cp); -} - -/* replace all possible paths with our CustomPath */ -static void handle_cypher_set_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) -{ - TargetEntry *te; - FuncExpr *fe; - List *custom_private; - CustomPath *cp; - - /* Add the pattern to the CustomPath */ - te = (TargetEntry *)llast(rte->subquery->targetList); - fe = (FuncExpr *)te->expr; - /* pass the const that holds the data structure to the path. */ - custom_private = fe->args; - - cp = create_cypher_set_path(root, rel, custom_private); - - /* Discard any preexisting paths */ - rel->pathlist = NIL; - rel->partial_pathlist = NIL; - - add_path(rel, (Path *)cp); -} - -/* replace all possible paths with our CustomPath */ -static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, - Index rti, RangeTblEntry *rte) +static void replace_with_cypher_dml_path(PlannerInfo *root, RelOptInfo *rel, + RangeTblEntry *rte, + cypher_path_factory factory) { TargetEntry *te; FuncExpr *fe; @@ -308,7 +205,7 @@ static void handle_cypher_merge_clause(PlannerInfo *root, RelOptInfo *rel, /* pass the const that holds the data structure to the path. */ custom_private = fe->args; - cp = create_cypher_merge_path(root, rel, custom_private); + cp = factory(root, rel, custom_private); /* Discard any preexisting paths */ rel->pathlist = NIL; diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index 934859ba8..a7ffd79e7 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -1840,6 +1840,11 @@ static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, Oid source_oid = exprType(expr); Oid target_oid = target_types[foreach_current_index(lc)]; + if (source_oid == target_oid) + { + continue; + } + /* errors out if cast not possible */ expr = coerce_expr_flexible(&cpstate->pstate, expr, source_oid, target_oid, -1, true); diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index b2792ebe7..613697b4f 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -46,6 +46,7 @@ #include "nodes/nodes.h" #include "utils/acl.h" #include "utils/ag_cache.h" +#include "utils/array.h" #include "utils/builtins.h" #include "utils/catcache.h" #include "executor/cypher_utils.h" @@ -196,6 +197,10 @@ static Datum agtype_array_element_impl(FunctionCallInfo fcinfo, static Datum process_access_operator_result(FunctionCallInfo fcinfo, agtype_value *agtv, bool as_text); +static Datum process_access_operator_result_no_copy(FunctionCallInfo fcinfo, + agtype_value *agtv, + bool as_text, + bool needs_free); /* typecast functions */ static void agtype_typecast_object(agtype_in_state *state, char *annotation); static void agtype_typecast_array(agtype_in_state *state, char *annotation); @@ -4285,50 +4290,187 @@ static Datum process_access_operator_result(FunctionCallInfo fcinfo, PG_RETURN_NULL(); } +static Datum process_access_operator_result_no_copy(FunctionCallInfo fcinfo, + agtype_value *agtv, + bool as_text, + bool needs_free) +{ + if (agtv != NULL) + { + if (as_text) + { + text *result; + + if (agtv->type == AGTV_BINARY) + { + StringInfo out = makeStringInfo(); + agtype_container *agtc = + (agtype_container *)agtv->val.binary.data; + char *str; + + str = agtype_to_cstring_worker(out, agtc, + agtv->val.binary.len, + false, true); + result = cstring_to_text(str); + } + else + { + result = agtype_value_to_text(agtv, false); + } + + free_agtype_value_no_copy(agtv, needs_free); + + if (result) + { + PG_RETURN_TEXT_P(result); + } + + PG_RETURN_NULL(); + } + else + { + agtype *result = agtype_value_to_agtype(agtv); + + free_agtype_value_no_copy(agtv, needs_free); + AG_RETURN_AGTYPE_P(result); + } + } + + PG_RETURN_NULL(); +} + Datum agtype_array_element_impl(FunctionCallInfo fcinfo, agtype *agtype_in, int element, bool as_text) { - agtype_value *v; + agtype_value value; + bool value_needs_free = false; + uint32 size; if (!AGT_ROOT_IS_ARRAY(agtype_in)) { PG_RETURN_NULL(); } - v = execute_array_access_operator_internal(agtype_in, NULL, element); + size = AGT_ROOT_COUNT(agtype_in); - return process_access_operator_result(fcinfo, v, as_text); + if (element < 0) + { + element = size + element; + } + + if (element < 0 || (uint32)element >= size) + { + PG_RETURN_NULL(); + } + + if (!get_ith_agtype_value_from_container_no_copy(&agtype_in->root, element, + &value, &value_needs_free)) + { + PG_RETURN_NULL(); + } + + return process_access_operator_result_no_copy(fcinfo, &value, as_text, + value_needs_free); } Datum agtype_object_field_impl(FunctionCallInfo fcinfo, agtype *agtype_in, char *key, int key_len, bool as_text) { agtype_value *v; - agtype* process_agtype; if (AGT_ROOT_IS_SCALAR(agtype_in)) { - agtype_value *process_agtv = extract_entity_properties(agtype_in, - false); - if (!process_agtv) + agtype_value scalar_value; + agtype_value *properties; + bool scalar_needs_free = false; + + get_scalar_agtype_value_no_copy(agtype_in, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) { + properties = &scalar_value.val.object.pairs[2].value; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + properties = &scalar_value; + } + + if (properties == NULL || properties->type == AGTV_NULL) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_NULL(); } - process_agtype = agtype_value_to_agtype(process_agtv); - } - else - { - process_agtype = agtype_in; + if (properties->type != AGTV_OBJECT && + (properties->type != AGTV_BINARY || + !AGTYPE_CONTAINER_IS_OBJECT(properties->val.binary.data))) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + PG_RETURN_NULL(); + } + + v = execute_map_access_operator_internal(NULL, properties, + key, key_len); + if (v == NULL || v->type == AGTV_NULL) + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + PG_RETURN_NULL(); + } + + if (as_text) + { + text *result; + + if (v->type == AGTV_BINARY) + { + StringInfo out = makeStringInfo(); + agtype_container *agtc = (agtype_container *)v->val.binary.data; + char *str; + + str = agtype_to_cstring_worker(out, agtc, v->val.binary.len, + false, true); + result = cstring_to_text(str); + } + else + { + result = agtype_value_to_text(v, false); + } + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + + if (result) + { + PG_RETURN_TEXT_P(result); + } + + PG_RETURN_NULL(); + } + else + { + agtype *result = agtype_value_to_agtype(v); + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + AG_RETURN_AGTYPE_P(result); + } } - if (!AGT_ROOT_IS_OBJECT(process_agtype)) + if (!AGT_ROOT_IS_OBJECT(agtype_in)) { PG_RETURN_NULL(); } - v = execute_map_access_operator_internal(process_agtype, NULL, - key, key_len); + v = execute_map_access_operator_internal(agtype_in, NULL, key, key_len); return process_access_operator_result(fcinfo, v, as_text); } @@ -9430,26 +9572,33 @@ Datum age_split(PG_FUNCTION_ARGS) if (PointerIsValid(DatumGetPointer(text_array))) { ArrayType *array = DatumGetArrayTypeP(text_array); + ArrayIterator iterator; agtype_in_state result; - Datum *elements; - int nelements; + Datum element; + bool is_null; - /* zero the state and deconstruct the ArrayType to TEXTOID */ + /* zero the state and stream the ArrayType to avoid extra arrays */ memset(&result, 0, sizeof(agtype_in_state)); - deconstruct_array(array, TEXTOID, -1, false, 'i', &elements, NULL, - &nelements); + iterator = array_create_iterator(array, 0, NULL); /* open the agtype array */ result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, NULL); /* add the values */ - for (i = 0; i < nelements; i++) + while (array_iterate(iterator, &element, &is_null)) { - text *elem = DatumGetTextPP(elements[i]); + text *elem; char *string; int string_len; agtype_value agtv_string; + if (is_null) + { + continue; + } + + elem = DatumGetTextPP(element); + /* get the string element from the array */ string = VARDATA_ANY(elem); string_len = VARSIZE_ANY_EXHDR(elem); @@ -9463,6 +9612,8 @@ Datum age_split(PG_FUNCTION_ARGS) &agtv_string); } + array_free_iterator(iterator); + /* close the array */ result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); diff --git a/src/backend/utils/adt/agtype_ops.c b/src/backend/utils/adt/agtype_ops.c index 1b786fc87..4e93f50df 100644 --- a/src/backend/utils/adt/agtype_ops.c +++ b/src/backend/utils/adt/agtype_ops.c @@ -27,6 +27,7 @@ #include #include "utils/agtype.h" +#include "utils/array.h" #include "utils/datum.h" #include "utils/builtins.h" #include "utils/float.h" @@ -47,6 +48,11 @@ static bool parse_agtype_index_string(char *str, int len, long *lindex); static void get_scalar_agtype_value_no_copy(agtype *agt, agtype_value *value, bool *needs_free); static void free_agtype_value_no_copy(agtype_value *value, bool needs_free); +static agtype_value *find_agtype_value_from_agtype_value_object( + agtype_value *object, agtype_value *key); +static agtype_container *get_entity_properties_container_no_copy( + agtype *agt, bool error_on_scalar, agtype_value *scalar_value, + bool *scalar_needs_free, agtype **owned_properties); static void concat_to_agtype_string(agtype_value *result, char *lhs, int llen, char *rhs, int rlen) @@ -187,6 +193,85 @@ static void free_agtype_value_no_copy(agtype_value *value, bool needs_free) } } +static agtype_value *find_agtype_value_from_agtype_value_object( + agtype_value *object, agtype_value *key) +{ + if (object == NULL || key->type != AGTV_STRING) + { + return NULL; + } + + if (object->type == AGTV_OBJECT) + { + return get_agtype_value_object_value(object, key->val.string.val, + key->val.string.len); + } + + if (object->type == AGTV_BINARY && + AGTYPE_CONTAINER_IS_OBJECT(object->val.binary.data)) + { + return find_agtype_value_from_container(object->val.binary.data, + AGT_FOBJECT, key); + } + + return NULL; +} + +static agtype_container *get_entity_properties_container_no_copy( + agtype *agt, bool error_on_scalar, agtype_value *scalar_value, + bool *scalar_needs_free, agtype **owned_properties) +{ + agtype_value *properties = NULL; + + *scalar_needs_free = false; + *owned_properties = NULL; + + if (!AGT_ROOT_IS_SCALAR(agt) || !AGTE_IS_AGTYPE(agt->root.children[0])) + { + return &agt->root; + } + + get_scalar_agtype_value_no_copy(agt, scalar_value, scalar_needs_free); + + if (scalar_value->type == AGTV_VERTEX) + { + properties = &scalar_value->val.object.pairs[2].value; + } + else if (scalar_value->type == AGTV_EDGE) + { + properties = &scalar_value->val.object.pairs[4].value; + } + else if (scalar_value->type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else if (error_on_scalar) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } + else + { + properties = scalar_value; + } + + if (properties == NULL || properties->type == AGTV_NULL) + { + return NULL; + } + + if (properties->type == AGTV_BINARY) + { + return properties->val.binary.data; + } + + *owned_properties = agtype_value_to_agtype(properties); + return &(*owned_properties)->root; +} + Datum get_numeric_datum_from_agtype_value(agtype_value *agtv) { switch (agtv->type) @@ -1485,11 +1570,6 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) agtype_value *v = NULL; bool aval_needs_free = false; - if (AGT_ROOT_IS_SCALAR(agt)) - { - agt = agtype_value_to_agtype(extract_entity_properties(agt, false)); - } - if (AGT_ROOT_IS_SCALAR(key)) { get_scalar_agtype_value_no_copy(key, aval, &aval_needs_free); @@ -1499,6 +1579,47 @@ Datum agtype_exists_agtype(PG_FUNCTION_ARGS) PG_RETURN_BOOL(false); } + if (AGT_ROOT_IS_SCALAR(agt)) + { + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool found = false; + + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + agt = agtype_value_to_agtype(extract_entity_properties(agt, + false)); + goto search_container; + } + + found = find_agtype_value_from_agtype_value_object(properties, + aval) != NULL; + + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); + free_agtype_value_no_copy(aval, aval_needs_free); + PG_RETURN_BOOL(found); + } + +search_container: if (AGT_ROOT_IS_OBJECT(agt) && aval->type == AGTV_STRING) { @@ -1530,10 +1651,38 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool use_scalar_properties = false; if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) @@ -1542,7 +1691,17 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) { if (IS_A_AGTYPE_SCALAR(&elem)) { - if (AGT_ROOT_IS_OBJECT(agt) && + if (use_scalar_properties) + { + if (find_agtype_value_from_agtype_value_object(properties, + &elem)) + { + free_agtype_value_no_copy(&scalar_value, + scalar_needs_free); + PG_RETURN_BOOL(true); + } + } + else if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, AGT_FOBJECT, @@ -1561,6 +1720,7 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) } else { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } } @@ -1571,6 +1731,7 @@ Datum agtype_exists_any_agtype(PG_FUNCTION_ARGS) errmsg("invalid agtype value for right operand"))); } + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } @@ -1585,10 +1746,38 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) agtype *keys = AG_GET_ARG_AGTYPE_P(1); agtype_value elem; agtype_iterator *it = NULL; + agtype_value scalar_value; + agtype_value *properties = NULL; + bool scalar_needs_free = false; + bool use_scalar_properties = false; if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + get_scalar_agtype_value_no_copy(agt, &scalar_value, + &scalar_needs_free); + + if (scalar_value.type == AGTV_VERTEX) + { + properties = &scalar_value.val.object.pairs[2].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_EDGE) + { + properties = &scalar_value.val.object.pairs[4].value; + use_scalar_properties = true; + } + else if (scalar_value.type == AGTV_PATH) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot extract properties from an agtype path"))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("scalar object must be a vertex or edge"))); + } } if (!AGT_ROOT_IS_SCALAR(keys) && !AGT_ROOT_IS_OBJECT(keys)) @@ -1601,6 +1790,12 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) { continue; } + else if (use_scalar_properties && + find_agtype_value_from_agtype_value_object(properties, + &elem)) + { + continue; + } else if (AGT_ROOT_IS_OBJECT(agt) && (&elem)->type == AGTV_STRING && find_agtype_value_from_container(&agt->root, @@ -1618,11 +1813,14 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) } else { + free_agtype_value_no_copy(&scalar_value, + scalar_needs_free); PG_RETURN_BOOL(false); } } else { + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(false); } } @@ -1633,6 +1831,7 @@ Datum agtype_exists_all_agtype(PG_FUNCTION_ARGS) errmsg("invalid agtype value for right operand"))); } + free_agtype_value_no_copy(&scalar_value, scalar_needs_free); PG_RETURN_BOOL(true); } @@ -1647,6 +1846,15 @@ Datum agtype_contains(PG_FUNCTION_ARGS) agtype_iterator *property_it = NULL; agtype *properties = NULL; agtype *constraints = NULL; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1656,31 +1864,29 @@ Datum agtype_contains(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } - - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) - { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_OBJECT(properties) != AGT_ROOT_IS_OBJECT(constraints)) + if (properties_container == NULL || constraints_container == NULL || + AGTYPE_CONTAINER_IS_OBJECT(properties_container) != + AGTYPE_CONTAINER_IS_OBJECT(constraints_container)) { + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); PG_RETURN_BOOL(false); } - property_it = agtype_iterator_init(&properties->root); - constraint_it = agtype_iterator_init(&constraints->root); + property_it = agtype_iterator_init(properties_container); + constraint_it = agtype_iterator_init(constraints_container); + result = agtype_deep_contains(&property_it, &constraint_it, false); - PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it, false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_contained_by_top_level); @@ -1694,6 +1900,15 @@ Datum agtype_contained_by_top_level(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it, *property_it; agtype *properties, *constraints; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1703,26 +1918,27 @@ Datum agtype_contained_by_top_level(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) + if (properties_container == NULL || constraints_container == NULL) { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(false); } - constraint_it = agtype_iterator_init(&constraints->root); - property_it = agtype_iterator_init(&properties->root); + constraint_it = agtype_iterator_init(constraints_container); + property_it = agtype_iterator_init(properties_container); + result = agtype_deep_contains(&constraint_it, &property_it, true); - PG_RETURN_BOOL(agtype_deep_contains(&constraint_it, &property_it, true)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } @@ -1735,6 +1951,15 @@ Datum agtype_contained_by(PG_FUNCTION_ARGS) { agtype_iterator *constraint_it, *property_it; agtype *properties, *constraints; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1744,26 +1969,27 @@ Datum agtype_contained_by(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) + if (properties_container == NULL || constraints_container == NULL) { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(false); } - constraint_it = agtype_iterator_init(&constraints->root); - property_it = agtype_iterator_init(&properties->root); + constraint_it = agtype_iterator_init(constraints_container); + property_it = agtype_iterator_init(properties_container); + result = agtype_deep_contains(&constraint_it, &property_it, false); - PG_RETURN_BOOL(agtype_deep_contains(&constraint_it, &property_it, false)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_contains_top_level); @@ -1779,6 +2005,15 @@ Datum agtype_contains_top_level(PG_FUNCTION_ARGS) agtype_iterator *property_it = NULL; agtype *properties = NULL; agtype *constraints = NULL; + agtype *owned_properties = NULL; + agtype *owned_constraints = NULL; + agtype_value properties_scalar; + agtype_value constraints_scalar; + agtype_container *properties_container; + agtype_container *constraints_container; + bool properties_needs_free = false; + bool constraints_needs_free = false; + bool result; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -1788,31 +2023,29 @@ Datum agtype_contains_top_level(PG_FUNCTION_ARGS) properties = AG_GET_ARG_AGTYPE_P(0); constraints = AG_GET_ARG_AGTYPE_P(1); - if (AGT_ROOT_IS_SCALAR(properties) - && AGTE_IS_AGTYPE(properties->root.children[0])) - { - properties = - agtype_value_to_agtype(extract_entity_properties(properties, - false)); - } - - if (AGT_ROOT_IS_SCALAR(constraints) - && AGTE_IS_AGTYPE(constraints->root.children[0])) - { - constraints = - agtype_value_to_agtype(extract_entity_properties(constraints, - false)); - } + properties_container = get_entity_properties_container_no_copy( + properties, false, &properties_scalar, &properties_needs_free, + &owned_properties); + constraints_container = get_entity_properties_container_no_copy( + constraints, false, &constraints_scalar, &constraints_needs_free, + &owned_constraints); - if (AGT_ROOT_IS_OBJECT(properties) != AGT_ROOT_IS_OBJECT(constraints)) + if (properties_container == NULL || constraints_container == NULL || + AGTYPE_CONTAINER_IS_OBJECT(properties_container) != + AGTYPE_CONTAINER_IS_OBJECT(constraints_container)) { + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); PG_RETURN_BOOL(false); } - property_it = agtype_iterator_init(&properties->root); - constraint_it = agtype_iterator_init(&constraints->root); + property_it = agtype_iterator_init(properties_container); + constraint_it = agtype_iterator_init(constraints_container); + result = agtype_deep_contains(&property_it, &constraint_it, true); - PG_RETURN_BOOL(agtype_deep_contains(&property_it, &constraint_it, true)); + free_agtype_value_no_copy(&properties_scalar, properties_needs_free); + free_agtype_value_no_copy(&constraints_scalar, constraints_needs_free); + PG_RETURN_BOOL(result); } PG_FUNCTION_INFO_V1(agtype_exists); @@ -1852,35 +2085,37 @@ Datum agtype_exists_any(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - int i; - Datum *key_datums; - bool *key_nulls; - int elem_count; + ArrayIterator iterator; + Datum key_datum; + bool key_null; - deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, - &elem_count); + iterator = array_create_iterator(keys, 0, NULL); - for (i = 0; i < elem_count; i++) + while (array_iterate(iterator, &key_datum, &key_null)) { + text *key; agtype_value strVal; - if (key_nulls[i]) + if (key_null) { continue; } + key = DatumGetTextPP(key_datum); strVal.type = AGTV_STRING; - strVal.val.string.val = VARDATA(key_datums[i]); - strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + strVal.val.string.val = VARDATA_ANY(key); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key); if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) != NULL) { + array_free_iterator(iterator); PG_RETURN_BOOL(true); } } + array_free_iterator(iterator); PG_RETURN_BOOL(false); } @@ -1893,35 +2128,37 @@ Datum agtype_exists_all(PG_FUNCTION_ARGS) { agtype *agt = AG_GET_ARG_AGTYPE_P(0); ArrayType *keys = PG_GETARG_ARRAYTYPE_P(1); - int i; - Datum *key_datums; - bool *key_nulls; - int elem_count; + ArrayIterator iterator; + Datum key_datum; + bool key_null; - deconstruct_array(keys, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, - &elem_count); + iterator = array_create_iterator(keys, 0, NULL); - for (i = 0; i < elem_count; i++) + while (array_iterate(iterator, &key_datum, &key_null)) { + text *key; agtype_value strVal; - if (key_nulls[i]) + if (key_null) { continue; } + key = DatumGetTextPP(key_datum); strVal.type = AGTV_STRING; - strVal.val.string.val = VARDATA(key_datums[i]); - strVal.val.string.len = VARSIZE(key_datums[i]) - VARHDRSZ; + strVal.val.string.val = VARDATA_ANY(key); + strVal.val.string.len = VARSIZE_ANY_EXHDR(key); if (find_agtype_value_from_container(&agt->root, AGT_FOBJECT | AGT_FARRAY, &strVal) == NULL) { + array_free_iterator(iterator); PG_RETURN_BOOL(false); } } + array_free_iterator(iterator); PG_RETURN_BOOL(true); } @@ -2215,7 +2452,10 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) int i; bool have_object = false, have_array = false; agtype_value *agtvp = NULL; + agtype_value root_scalar_value; agtype_value scalar_value; + agtype *owned_agt = NULL; + bool root_scalar_needs_free = false; bool scalar_needs_free = false; agtype_value tv; agtype_container *container; @@ -2226,20 +2466,39 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) errmsg("right operand must be an array"))); } + npath = AGT_ROOT_COUNT(path); if (AGT_ROOT_IS_SCALAR(agt)) { - agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + if (!AGTE_IS_AGTYPE(agt->root.children[0]) || npath <= 0) + { + agt = agtype_value_to_agtype(extract_entity_properties(agt, true)); + container = &agt->root; + } + else + { + container = get_entity_properties_container_no_copy( + agt, true, &root_scalar_value, &root_scalar_needs_free, + &owned_agt); + } + } + else + { + container = &agt->root; } - npath = AGT_ROOT_COUNT(path); - container = &agt->root; + if (container == NULL) + { + free_agtype_value_no_copy(&root_scalar_value, root_scalar_needs_free); + PG_RETURN_NULL(); + } /* Identify whether we have object, array, or scalar at top-level */ - if (AGT_ROOT_IS_OBJECT(agt)) + if (AGTYPE_CONTAINER_IS_OBJECT(container)) { have_object = true; } - else if (AGT_ROOT_IS_ARRAY(agt) && !AGT_ROOT_IS_SCALAR(agt)) + else if (AGTYPE_CONTAINER_IS_ARRAY(container) && + !AGTYPE_CONTAINER_IS_SCALAR(container)) { have_array = true; } @@ -2269,11 +2528,17 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) { if (as_text) { - PG_RETURN_TEXT_P(agtype_container_to_text(container, VARSIZE(agt))); + text *result = agtype_container_to_text(container, VARSIZE(agt)); + + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); + PG_RETURN_TEXT_P(result); } else { /* not text mode - just hand back the agtype */ + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); AG_RETURN_AGTYPE_P(agt); } } @@ -2316,18 +2581,24 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) cur_key.val.string.len, &lindex)) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } } else { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } if (lindex > INT_MAX || lindex < INT_MIN) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2351,6 +2622,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) if (-lindex > nelements) { + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2374,6 +2647,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) else { free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2381,6 +2656,8 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) if (agtvp == NULL) { free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); free_agtype_value_no_copy(&cur_key, cur_key_needs_free); PG_RETURN_NULL(); } @@ -2420,18 +2697,23 @@ static Datum get_agtype_path_all(FunctionCallInfo fcinfo, bool as_text) agtvp->val.string.len); free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); PG_RETURN_TEXT_P(result); } if (agtvp->type == AGTV_NULL) { free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, + root_scalar_needs_free); PG_RETURN_NULL(); } } res = agtype_value_to_agtype(agtvp); free_agtype_value_no_copy(agtvp, scalar_needs_free); + free_agtype_value_no_copy(&root_scalar_value, root_scalar_needs_free); if (as_text) { From 1b10f960f6fa39775f2c65aa4ad55d4e5cb00c62 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:39 +0900 Subject: [PATCH 13/15] Broaden AGE metadata caches Widen and refresh AGE metadata caches used by global graph load, label validity checks, catalog writes, and graph load setup. The change keeps compact global-graph label arrays, shared graph label names, metadata miss caching, generation refresh on catalog writes, created-label relid reuse, DML and parser label lookup caches, and graph-cache setup during load in one reviewable cache-generation pass. --- src/backend/catalog/ag_graph.c | 6 + src/backend/catalog/ag_label.c | 4 + src/backend/commands/label_commands.c | 25 +-- src/backend/executor/cypher_create.c | 11 +- src/backend/executor/cypher_delete.c | 24 ++- src/backend/executor/cypher_merge.c | 20 +- src/backend/executor/cypher_set.c | 38 ++-- src/backend/executor/cypher_utils.c | 104 +++++++++- src/backend/parser/cypher_clause.c | 237 +++++++++++------------ src/backend/parser/cypher_parse_node.c | 62 ++++++ src/backend/utils/adt/age_global_graph.c | 137 +++++++------ src/backend/utils/adt/age_vle.c | 91 ++++++--- src/backend/utils/adt/agtype.c | 21 +- src/backend/utils/cache/ag_cache.c | 166 ++++++++-------- src/backend/utils/graph_generation.c | 27 ++- src/backend/utils/load/age_load.c | 32 +-- src/include/commands/label_commands.h | 4 +- src/include/executor/cypher_utils.h | 14 +- src/include/parser/cypher_parse_node.h | 4 + src/include/utils/ag_cache.h | 2 + 20 files changed, 681 insertions(+), 348 deletions(-) diff --git a/src/backend/catalog/ag_graph.c b/src/backend/catalog/ag_graph.c index 51f2a9256..c77af98a6 100644 --- a/src/backend/catalog/ag_graph.c +++ b/src/backend/catalog/ag_graph.c @@ -59,6 +59,8 @@ void insert_graph(const Name graph_name, const Oid nsp_id) */ CatalogTupleInsert(ag_graph, tuple); + invalidate_graph_cache(); + table_close(ag_graph, RowExclusiveLock); } @@ -87,6 +89,8 @@ void delete_graph(const Name graph_name) CatalogTupleDelete(ag_graph, &tuple->t_self); + invalidate_graph_cache(); + systable_endscan(scan_desc); table_close(ag_graph, RowExclusiveLock); } @@ -135,6 +139,8 @@ void update_graph_name(const Name graph_name, const Name new_name) /* update the current tuple with the new tuple */ CatalogTupleUpdate(ag_graph, &cur_tuple->t_self, new_tuple); + invalidate_graph_cache(); + /* end scan and close ag_graph */ systable_endscan(scan_desc); table_close(ag_graph, RowExclusiveLock); diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index b0c6d834d..dca0ebf0e 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -90,6 +90,8 @@ void insert_label(const char *label_name, Oid graph_oid, int32 label_id, */ CatalogTupleInsert(ag_label, tuple); + invalidate_label_cache(); + table_close(ag_label, RowExclusiveLock); } @@ -118,6 +120,8 @@ void delete_label(Oid relation) CatalogTupleDelete(ag_label, &tuple->t_self); + invalidate_label_cache(); + systable_endscan(scan_desc); table_close(ag_label, RowExclusiveLock); } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index 70f640e4d..06959b758 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -85,9 +85,9 @@ static void create_index_on_column(char *schema_name, char *rel_name, char *colname, bool unique); -static void create_label_with_graph_cache(char *graph_name, char *label_name, - char label_type, List *parents, - graph_cache_data *cache_data); +static Oid create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data); PG_FUNCTION_INFO_V1(age_is_valid_label_name); @@ -312,11 +312,10 @@ Datum create_elabel(PG_FUNCTION_ARGS) /* * For the new label, create an entry in ag_catalog.ag_label, create a - * new table and sequence. Returns the oid from the new tuple in - * ag_catalog.ag_label. + * new table and sequence. Returns the new label relation OID. */ -void create_label(char *graph_name, char *label_name, char label_type, - List *parents) +Oid create_label(char *graph_name, char *label_name, char label_type, + List *parents) { graph_cache_data *cache_data; @@ -333,13 +332,13 @@ void create_label(char *graph_name, char *label_name, char label_type, errmsg("graph \"%s\" does not exist", graph_name))); } - create_label_with_graph_cache(graph_name, label_name, label_type, parents, - cache_data); + return create_label_with_graph_cache(graph_name, label_name, label_type, + parents, cache_data); } -static void create_label_with_graph_cache(char *graph_name, char *label_name, - char label_type, List *parents, - graph_cache_data *cache_data) +static Oid create_label_with_graph_cache(char *graph_name, char *label_name, + char label_type, List *parents, + graph_cache_data *cache_data) { Oid graph_oid; Oid nsp_id; @@ -382,6 +381,8 @@ static void create_label_with_graph_cache(char *graph_name, char *label_name, relation_id, seq_name); CommandCounterIncrement(); + + return relation_id; } /* diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index bd93bb1c0..04e3de3b2 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -145,6 +145,8 @@ static void begin_cypher_create(CustomScanState *node, EState *estate, css->entity_exists_index_cache = create_entity_exists_index_cache("create_entity_exists_index_cache"); + css->entity_exists_label_relation_cache = + create_label_relation_cache("create_entity_exists_label_relation_cache"); Increment_Estate_CommandId(estate); } @@ -273,6 +275,12 @@ static void end_cypher_create(CustomScanState *node) css->entity_exists_index_cache = NULL; } + if (css->entity_exists_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->entity_exists_label_relation_cache); + css->entity_exists_label_relation_cache = NULL; + } + foreach (lc, css->pattern) { cypher_create_path *path = lfirst(lc); @@ -607,7 +615,8 @@ static Datum create_vertex(cypher_create_custom_scan_state *css, { if (!entity_exists_with_cache(estate, css->graph_oid, DATUM_GET_GRAPHID(id), - css->entity_exists_index_cache)) + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 8065afe6b..3b446cc81 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -220,6 +220,12 @@ static void end_cypher_delete(CustomScanState *node) css->result_rel_info_cache = NULL; } + if (css->label_relation_cache != NULL) + { + destroy_label_relation_cache(css->label_relation_cache); + css->label_relation_cache = NULL; + } + if (css->vertex_id_htab != NULL) { hash_destroy(css->vertex_id_htab); @@ -250,6 +256,8 @@ static void init_delete_caches(cypher_delete_custom_scan_state *css) css->result_rel_info_cache = create_entity_result_rel_info_cache("delete_result_rel_info_cache"); + css->label_relation_cache = + create_label_relation_cache("delete_label_relation_cache"); } static HTAB *create_delete_vertex_htab(void) @@ -525,9 +533,9 @@ static void process_delete_list(CustomScanState *node) TableScanDesc scan_desc = NULL; ResultRelInfo *resultRelInfo; HeapTuple heap_tuple = NULL; - label_cache_data *label_cache; int entity_position; Oid relid; + Oid label_relation; Relation rel; int id_attr_num; Oid index_oid = InvalidOid; @@ -539,6 +547,7 @@ static void process_delete_list(CustomScanState *node) bool found_idx_entry; RLSCacheEntry *rls_entry; bool found_rls_entry; + int32 label_id; entity_position = delete_item_positions[i]; @@ -548,20 +557,21 @@ static void process_delete_list(CustomScanState *node) extract_entity(node, scanTupleSlot, entity_position, &entity_type, &entity_id); + label_id = GET_LABEL_ID(entity_id); - label_cache = search_label_graph_oid_cache_cached( - css->delete_data->graph_oid, GET_LABEL_ID(entity_id)); - if (label_cache == NULL) + if (!get_label_relation_from_cache(css->label_relation_cache, + css->delete_data->graph_oid, + label_id, &label_relation, NULL)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("label id %lu does not exist", - GET_LABEL_ID(entity_id)))); + errmsg("label id %d does not exist", + label_id))); } resultRelInfo = get_entity_result_rel_info(estate, css->result_rel_info_cache, - label_cache->relation); + label_relation); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 1cc12d54f..92cef8239 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -263,6 +263,8 @@ static void begin_cypher_merge(CustomScanState *node, EState *estate, css->entity_exists_index_cache = create_entity_exists_index_cache("merge_entity_exists_index_cache"); + css->entity_exists_label_relation_cache = + create_label_relation_cache("merge_entity_exists_label_relation_cache"); Increment_Estate_CommandId(estate); } @@ -601,7 +603,8 @@ static path_entry **prebuild_path(CustomScanState *node) { if (!entity_exists_with_cache(estate, css->graph_oid, entry->id, - css->entity_exists_index_cache)) + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -1222,6 +1225,12 @@ static void end_cypher_merge(CustomScanState *node) css->entity_exists_index_cache = NULL; } + if (css->entity_exists_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->entity_exists_label_relation_cache); + css->entity_exists_label_relation_cache = NULL; + } + if (css->update_qual_cache != NULL) { hash_destroy(css->update_qual_cache); @@ -1240,6 +1249,12 @@ static void end_cypher_merge(CustomScanState *node) css->update_result_rel_info_cache = NULL; } + if (css->update_label_relation_cache != NULL) + { + destroy_label_relation_cache(css->update_label_relation_cache); + css->update_label_relation_cache = NULL; + } + foreach (lc, path->target_nodes) { cypher_target_node *cypher_node = (cypher_target_node *)lfirst(lc); @@ -1644,7 +1659,8 @@ static Datum merge_vertex(cypher_merge_custom_scan_state *css, { if (!entity_exists_with_cache(estate, css->graph_oid, DATUM_GET_GRAPHID(id), - css->entity_exists_index_cache)) + css->entity_exists_index_cache, + css->entity_exists_label_relation_cache)) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index 84233e8ff..bbe8ba691 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -133,6 +133,8 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, &css->result_rel_info_cache, "set_qual_cache", "set_index_cache", "set_result_rel_info_cache"); + css->label_relation_cache = + create_label_relation_cache("set_label_relation_cache"); Increment_Estate_CommandId(estate); } @@ -584,6 +586,7 @@ bool apply_update_list(CustomScanState *node, HTAB *qual_cache = NULL; HTAB *index_cache = NULL; HTAB *result_rel_info_cache = NULL; + HTAB *label_relation_cache = NULL; bool local_caches = false; bool graph_mutated = false; Oid graph_oid; @@ -596,6 +599,7 @@ bool apply_update_list(CustomScanState *node, qual_cache = css->qual_cache; index_cache = css->index_cache; result_rel_info_cache = css->result_rel_info_cache; + label_relation_cache = css->label_relation_cache; graph_oid = css->graph_oid; } else if (node->methods == &cypher_merge_exec_methods) @@ -605,7 +609,8 @@ bool apply_update_list(CustomScanState *node, if (css->update_qual_cache == NULL || css->update_index_cache == NULL || - css->update_result_rel_info_cache == NULL) + css->update_result_rel_info_cache == NULL || + css->update_label_relation_cache == NULL) { init_update_caches(&css->update_qual_cache, &css->update_index_cache, @@ -613,11 +618,14 @@ bool apply_update_list(CustomScanState *node, "merge_update_qual_cache", "merge_update_index_cache", "merge_update_result_rel_info_cache"); + css->update_label_relation_cache = + create_label_relation_cache("merge_update_label_relation_cache"); } qual_cache = css->update_qual_cache; index_cache = css->update_index_cache; result_rel_info_cache = css->update_result_rel_info_cache; + label_relation_cache = css->update_label_relation_cache; graph_oid = css->graph_oid; } else @@ -626,6 +634,8 @@ bool apply_update_list(CustomScanState *node, &result_rel_info_cache, "update_qual_cache", "update_index_cache", "update_result_rel_info_cache"); + label_relation_cache = + create_label_relation_cache("update_label_relation_cache"); local_caches = true; graph_oid = set_info->graph_oid; if (!OidIsValid(graph_oid)) @@ -657,7 +667,6 @@ bool apply_update_list(CustomScanState *node, agtype_value original_entity_value; agtype_value *original_properties; agtype_value *id; - label_cache_data *label_cache; agtype_value *startid = NULL; agtype_value *endid = NULL; agtype *original_entity; @@ -680,6 +689,7 @@ bool apply_update_list(CustomScanState *node, RLSCacheEntry *rls_entry = NULL; bool found_rls_entry; bool original_entity_needs_free = false; + int32 label_id; update_item = (cypher_update_item *)lfirst(lc); @@ -731,19 +741,14 @@ bool apply_update_list(CustomScanState *node, original_properties = AGTYPE_EDGE_GET_PROPERTIES(&original_entity_value); } - label_cache = search_label_graph_oid_cache_cached( - graph_oid, GET_LABEL_ID(id->val.int_value)); - if (label_cache == NULL) + label_id = GET_LABEL_ID(id->val.int_value); + if (!get_label_relation_from_cache(label_relation_cache, graph_oid, + label_id, &relid, &label_name)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("label id %lu does not exist", - GET_LABEL_ID(id->val.int_value)))); - } - label_name = NameStr(label_cache->name); - if (IS_AG_DEFAULT_LABEL(label_name)) - { - label_name = ""; + errmsg("label id %d does not exist", + label_id))); } /* @@ -886,7 +891,7 @@ bool apply_update_list(CustomScanState *node, estate->es_snapshot->curcid = GetCurrentCommandId(false); resultRelInfo = get_entity_result_rel_info( - estate, result_rel_info_cache, label_cache->relation); + estate, result_rel_info_cache, relid); rel = resultRelInfo->ri_RelationDesc; relid = RelationGetRelid(rel); @@ -1095,6 +1100,7 @@ bool apply_update_list(CustomScanState *node, hash_destroy(qual_cache); destroy_index_cache(index_cache, false); destroy_entity_result_rel_info_cache(result_rel_info_cache); + destroy_label_relation_cache(label_relation_cache); } return graph_mutated; @@ -1227,6 +1233,12 @@ static void end_cypher_set(CustomScanState *node) css->result_rel_info_cache = NULL; } + if (css->label_relation_cache != NULL) + { + destroy_label_relation_cache(css->label_relation_cache); + css->label_relation_cache = NULL; + } + ExecEndNode(node->ss.ps.lefttree); } diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index e6510874d..f27e62705 100644 --- a/src/backend/executor/cypher_utils.c +++ b/src/backend/executor/cypher_utils.c @@ -35,6 +35,7 @@ #include "utils/rls.h" #include "catalog/ag_label.h" +#include "commands/label_commands.h" #include "executor/cypher_utils.h" #include "utils/ag_cache.h" @@ -44,6 +45,13 @@ typedef struct EntityResultRelInfoCacheEntry ResultRelInfo *resultRelInfo; } EntityResultRelInfoCacheEntry; +typedef struct LabelRelationCacheEntry +{ + int32 label_id; + Oid relation; + char *label_name; +} LabelRelationCacheEntry; + /* RLS helper function declarations */ static void get_policies_for_relation(Relation relation, CmdType cmd, Oid user_id, List **permissive_policies, @@ -129,6 +137,71 @@ void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache) hash_destroy(result_rel_info_cache); } +HTAB *create_label_relation_cache(const char *name) +{ + HASHCTL hashctl; + + MemSet(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(int32); + hashctl.entrysize = sizeof(LabelRelationCacheEntry); + hashctl.hcxt = CurrentMemoryContext; + + return hash_create(name, 8, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +bool get_label_relation_from_cache(HTAB *label_relation_cache, Oid graph_oid, + int32 label_id, Oid *relation, + char **label_name) +{ + LabelRelationCacheEntry *entry; + bool found; + + entry = hash_search(label_relation_cache, &label_id, HASH_ENTER, &found); + if (!found) + { + label_cache_data *label; + char *cached_label_name; + + label = search_label_graph_oid_cache_cached(graph_oid, label_id); + if (label == NULL) + { + hash_search(label_relation_cache, &label_id, HASH_REMOVE, NULL); + return false; + } + + cached_label_name = NameStr(label->name); + entry->relation = label->relation; + entry->label_name = IS_AG_DEFAULT_LABEL(cached_label_name) ? + "" : pstrdup(cached_label_name); + } + + *relation = entry->relation; + if (label_name != NULL) + { + *label_name = entry->label_name; + } + + return true; +} + +void destroy_label_relation_cache(HTAB *label_relation_cache) +{ + HASH_SEQ_STATUS hash_seq; + LabelRelationCacheEntry *entry; + + hash_seq_init(&hash_seq, label_relation_cache); + while ((entry = hash_seq_search(&hash_seq)) != NULL) + { + if (entry->label_name != NULL && entry->label_name[0] != '\0') + { + pfree(entry->label_name); + } + } + + hash_destroy(label_relation_cache); +} + TupleTableSlot *populate_vertex_tts( TupleTableSlot *elemTupleSlot, agtype_value *id, agtype_value *properties) { @@ -238,7 +311,7 @@ void destroy_index_cache(HTAB *index_cache, bool close_relations) bool entity_exists(EState *estate, Oid graph_oid, graphid id) { - return entity_exists_with_cache(estate, graph_oid, id, NULL); + return entity_exists_with_cache(estate, graph_oid, id, NULL, NULL); } /* @@ -246,7 +319,7 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid id) * of an entity. */ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, - HTAB *index_cache) + HTAB *index_cache, HTAB *label_relation_cache) { label_cache_data *label; ScanKeyData scan_keys[1]; @@ -259,12 +332,31 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, CommandId saved_curcid; CommandId current_cid; IndexCacheEntry *cache_entry = NULL; + Oid relid; + int32 label_id; /* * Extract the label id from the graph id and get the table name * the entity is part of. */ - label = search_label_graph_oid_cache_cached(graph_oid, GET_LABEL_ID(id)); + label_id = GET_LABEL_ID(id); + if (label_relation_cache != NULL) + { + if (!get_label_relation_from_cache(label_relation_cache, graph_oid, + label_id, &relid, NULL)) + { + return false; + } + } + else + { + label = search_label_graph_oid_cache_cached(graph_oid, label_id); + if (label == NULL) + { + return false; + } + relid = label->relation; + } /* Setup the scan key to be the graphid */ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, @@ -291,12 +383,12 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, { bool found; - cache_entry = hash_search(index_cache, &label->relation, HASH_ENTER, + cache_entry = hash_search(index_cache, &relid, HASH_ENTER, &found); if (!found) { init_index_cache_entry(cache_entry); - cache_entry->rel = table_open(label->relation, AccessShareLock); + cache_entry->rel = table_open(relid, AccessShareLock); rel = cache_entry->rel; cache_entry->index_oid = find_usable_btree_index_for_attr(rel, 1); cache_entry->index_oid_cached = true; @@ -310,7 +402,7 @@ bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, } else { - rel = table_open(label->relation, AccessShareLock); + rel = table_open(relid, AccessShareLock); index_oid = find_usable_btree_index_for_attr(rel, 1); slot = NULL; } diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index edbfb32bc..9b6016cd6 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -135,17 +135,22 @@ static Relation open_label_relation_with_cache(cypher_parsestate *cpstate, int location, LOCKMODE lockmode, label_cache_data *label_cache); +static RangeVar *make_default_label_range_var(cypher_parsestate *cpstate, + char *label_name); /* match clause */ static Query *transform_cypher_match(cypher_parsestate *cpstate, cypher_clause *clause); static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause); +static Query *transform_cypher_match_pattern_internal( + cypher_parsestate *cpstate, cypher_clause *clause, bool valid_label); static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, - cypher_path *path); + cypher_path *path, bool valid_label); static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, - List *pattern, Node *where); + List *pattern, Node *where, + bool valid_label); static List *transform_match_path(cypher_parsestate *cpstate, Query *query, - cypher_path *path); + cypher_path *path, bool valid_label); static Expr *transform_cypher_edge(cypher_parsestate *cpstate, cypher_relationship *rel, List **target_list, bool valid_label); @@ -463,8 +468,7 @@ open_label_relation_with_cache(cypher_parsestate *cpstate, if (label_cache == NULL) { - label_cache = search_label_name_graph_cache_cached(label_name, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, label_name); } relid = label_cache != NULL ? label_cache->relation : InvalidOid; @@ -479,6 +483,12 @@ open_label_relation_with_cache(cypher_parsestate *cpstate, return table_open(relid, lockmode); } +static RangeVar *make_default_label_range_var(cypher_parsestate *cpstate, + char *label_name) +{ + return makeRangeVar(cpstate->graph_name, pstrdup(label_name), 2); +} + /* * Add required permissions to the label table for a given entity variable. * Looks up the entity by variable name, extracts its label, and adds @@ -514,8 +524,7 @@ add_entity_permissions(cypher_parsestate *cpstate, char *var_name, return; } - label_cache = search_label_name_graph_cache_cached(label, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, label); relid = label_cache != NULL ? label_cache->relation : InvalidOid; if (OidIsValid(relid)) { @@ -2947,8 +2956,7 @@ static bool match_check_valid_label(cypher_match *match, if (node->label) { label_cache_data *lcd = - search_label_name_graph_cache_cached( - node->label, cpstate->graph_oid); + get_label_cache_data(cpstate, node->label); if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) @@ -2966,8 +2974,7 @@ static bool match_check_valid_label(cypher_match *match, if (rel->label) { label_cache_data *lcd = - search_label_name_graph_cache_cached( - rel->label, cpstate->graph_oid); + get_label_cache_data(cpstate, rel->label); if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) { @@ -3044,6 +3051,8 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, { cypher_match *match_self = (cypher_match*) clause->self; Node *where = match_self->where; + bool has_dml = clause_chain_has_dml(clause->prev); + bool valid_label = true; /* @@ -3053,12 +3062,15 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, * cache, so the check is deferred to after transform_prev_cypher_clause() * for those cases. */ - if (!clause_chain_has_dml(clause->prev) && - !match_check_valid_label(match_self, cpstate)) + if (!has_dml) { - /* Label is invalid -- inject a false WHERE so the MATCH returns - * zero rows. No DML predecessor here, so constant-foldable is fine. */ - match_self->where = make_false_where_clause(false); + valid_label = match_check_valid_label(match_self, cpstate); + if (!valid_label) + { + /* Label is invalid -- inject a false WHERE so the MATCH returns + * zero rows. No DML predecessor here, so constant-foldable is fine. */ + match_self->where = make_false_where_clause(false); + } } /* @@ -3084,7 +3096,8 @@ static Query *transform_cypher_match(cypher_parsestate *cpstate, transform_cypher_match_pattern, clause, where); } - return transform_cypher_match_pattern(cpstate, clause); + return transform_cypher_match_pattern_internal(cpstate, clause, + valid_label); } /* @@ -3337,6 +3350,12 @@ static RangeTblEntry *transform_cypher_optional_match_clause(cypher_parsestate * static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, cypher_clause *clause) +{ + return transform_cypher_match_pattern_internal(cpstate, clause, true); +} + +static Query *transform_cypher_match_pattern_internal( + cypher_parsestate *cpstate, cypher_clause *clause, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; cypher_match *self = (cypher_match *)clause->self; @@ -3433,13 +3452,18 @@ static Query *transform_cypher_match_pattern(cypher_parsestate *cpstate, * Filter: false, eliminating the entire plan subtree -- * including the DML predecessor scan -- without executing it. */ - if (has_dml && !match_check_valid_label(self, cpstate)) + if (has_dml) { - where = make_false_where_clause(true); + valid_label = match_check_valid_label(self, cpstate); + if (!valid_label) + { + where = make_false_where_clause(true); + } } } - transform_match_pattern(cpstate, query, self->pattern, where); + transform_match_pattern(cpstate, query, self->pattern, where, + valid_label); } markTargetListOrigins(pstate, query->targetList); @@ -3831,7 +3855,8 @@ static ParseNamespaceItem *transform_RangeFunction(cypher_parsestate *cpstate, } static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, - List *pattern, Node *where) + List *pattern, Node *where, + bool valid_label) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc; @@ -3850,7 +3875,7 @@ static void transform_match_pattern(cypher_parsestate *cpstate, Query *query, /* get the path and transform it */ path = (cypher_path *) lfirst(lc); - qual = transform_match_path(cpstate, query, path); + qual = transform_match_path(cpstate, query, path, valid_label); quals = list_concat(quals, qual); } @@ -4434,8 +4459,7 @@ static List *make_edge_quals(cypher_parsestate *cpstate, static A_Expr *filter_vertices_on_label_id(cypher_parsestate *cpstate, Node *id_field, char *label) { - label_cache_data *lcd = search_label_name_graph_cache_cached( - label, cpstate->graph_oid); + label_cache_data *lcd = get_label_cache_data(cpstate, label); A_Const *n; FuncCall *fc; String *ag_catalog, *extract_label_id; @@ -4792,7 +4816,7 @@ static Node *create_property_constraints(cypher_parsestate *cpstate, * correct join tree, and enforce edge uniqueness. */ static List *transform_match_path(cypher_parsestate *cpstate, Query *query, - cypher_path *path) + cypher_path *path, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; List *qual = NIL; @@ -4801,7 +4825,7 @@ static List *transform_match_path(cypher_parsestate *cpstate, Query *query, List *join_quals; /* transform the entities in the path */ - entities = transform_match_entities(cpstate, query, path); + entities = transform_match_entities(cpstate, query, path, valid_label); /* create the path variable, if needed. */ if (path->var_name != NULL) @@ -4998,61 +5022,11 @@ static bool isa_special_VLE_case(cypher_path *path) return false; } -static bool path_check_valid_label(cypher_path *path, - cypher_parsestate *cpstate) -{ - ListCell *lc = NULL; - int i = 0; - - foreach (lc, path->path) - { - if (i % 2 == 0) - { - cypher_node *node = NULL; - - node = lfirst(lc); - - if (node->label) - { - label_cache_data *lcd = - search_label_name_graph_cache_cached(node->label, - cpstate->graph_oid); - - if (lcd == NULL || lcd->kind != LABEL_KIND_VERTEX) - { - return false; - } - } - } - else - { - cypher_relationship *rel = NULL; - - rel = lfirst(lc); - - if (rel->label) - { - label_cache_data *lcd = - search_label_name_graph_cache_cached(rel->label, - cpstate->graph_oid); - - if (lcd == NULL || lcd->kind != LABEL_KIND_EDGE) - { - return false; - } - } - } - i++; - } - - return true; -} - /* * Iterate through the path and construct all edges and necessary vertices */ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, - cypher_path *path) + cypher_path *path, bool valid_label) { ParseState *pstate = (ParseState *)cpstate; ListCell *lc = NULL; @@ -5061,10 +5035,8 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, bool node_declared_in_prev_clause = false; transform_entity *prev_entity = NULL; bool special_VLE_case = false; - bool valid_label = true; special_VLE_case = isa_special_VLE_case(path); - valid_label = path_check_valid_label(path, cpstate); /* * Iterate through every node in the path, construct the expr node @@ -6475,6 +6447,7 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, AttrNumber resno; ParseNamespaceItem *pnsi; label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; rel->type = LABEL_KIND_EDGE; rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; @@ -6530,8 +6503,7 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, parser_errposition(&cpstate->pstate, edge->location))); } - label_cache = search_label_name_graph_cache_cached(edge->label, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, edge->label); if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, @@ -6545,20 +6517,26 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, { List *parent; - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_EDGE); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_EDGE); parent = list_make1(rv); - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, - parent); + created_label_relid = create_label(cpstate->graph_name, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ - label_relation = open_label_relation_with_cache(cpstate, edge->label, - edge->location, - RowExclusiveLock, - label_cache); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -6623,8 +6601,7 @@ transform_create_cypher_node(cypher_parsestate *cpstate, List **target_list, if (node->label) { - label_cache = search_label_name_graph_cache_cached( - node->label, cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, node->label); if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -6803,6 +6780,7 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, int resno; ParseNamespaceItem *pnsi; label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; rel->type = LABEL_KIND_VERTEX; rel->tuple_position = InvalidAttrNumber; @@ -6823,29 +6801,34 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, rel->label_name = node->label; } - label_cache = search_label_name_graph_cache_cached(node->label, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, node->label); /* create the label entry if it does not exist */ if (label_cache == NULL) { List *parent; - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_VERTEX); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_VERTEX); parent = list_make1(rv); - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, - parent); + created_label_relid = create_label(cpstate->graph_name, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; - label_relation = open_label_relation_with_cache(cpstate, node->label, - node->location, - RowExclusiveLock, - label_cache); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8050,6 +8033,7 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; if (edge->name != NULL) { @@ -8092,8 +8076,7 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, } - label_cache = search_label_name_graph_cache_cached(edge->label, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, edge->label); if (label_cache != NULL && label_cache->kind == LABEL_KIND_VERTEX) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8109,21 +8092,27 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, * setup the default edge table as the parent table, that we * will inherit from. */ - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_EDGE); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_EDGE); parent = list_make1(rv); /* create the label */ - create_label(cpstate->graph_name, edge->label, LABEL_TYPE_EDGE, - parent); + created_label_relid = create_label(cpstate->graph_name, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ - label_relation = open_label_relation_with_cache(cpstate, edge->label, - edge->location, - RowExclusiveLock, - label_cache); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, edge->label, + edge->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); @@ -8161,6 +8150,7 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, RTEPermissionInfo *rte_pi; ParseNamespaceItem *pnsi; label_cache_data *label_cache = NULL; + Oid created_label_relid = InvalidOid; if (node->name != NULL) { @@ -8223,8 +8213,7 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, rel->label_name = node->label; } - label_cache = search_label_name_graph_cache_cached(node->label, - cpstate->graph_oid); + label_cache = get_label_cache_data(cpstate, node->label); if (label_cache != NULL && label_cache->kind == LABEL_KIND_EDGE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -8241,22 +8230,28 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, * setup the default vertex table as the parent table, that we * will inherit from. */ - rv = get_label_range_var(cpstate->graph_name, cpstate->graph_oid, - AG_DEFAULT_LABEL_VERTEX); + rv = make_default_label_range_var(cpstate, AG_DEFAULT_LABEL_VERTEX); parent = list_make1(rv); /* create the label */ - create_label(cpstate->graph_name, node->label, LABEL_TYPE_VERTEX, - parent); + created_label_relid = create_label(cpstate->graph_name, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; - label_relation = open_label_relation_with_cache(cpstate, node->label, - node->location, - RowExclusiveLock, - label_cache); + if (OidIsValid(created_label_relid)) + { + label_relation = table_open(created_label_relid, RowExclusiveLock); + } + else + { + label_relation = open_label_relation_with_cache(cpstate, node->label, + node->location, + RowExclusiveLock, + label_cache); + } /* Store the relid */ rel->relid = RelationGetRelid(label_relation); diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index ae2570f93..1adc489a5 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -29,6 +29,16 @@ #include "parser/cypher_parse_node.h" static void errpos_ecb(void *arg); +static cypher_parsestate *get_root_cypher_parsestate( + cypher_parsestate *cpstate); + +typedef struct label_cache_entry +{ + const char *label_name; + Oid graph_oid; + uint64 generation; + label_cache_data *label_cache; +} label_cache_entry; /* NOTE: sync the logic with make_parsestate() */ cypher_parsestate *make_cypher_parsestate(cypher_parsestate *parent_cpstate) @@ -143,3 +153,55 @@ char *get_next_default_alias(cypher_parsestate *cpstate) return alias_name; } + +label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, + const char *label_name) +{ + cypher_parsestate *root_cpstate = get_root_cypher_parsestate(cpstate); + ListCell *lc; + uint64 generation = get_label_cache_generation(); + label_cache_data *label_cache; + label_cache_entry *entry; + + foreach (lc, root_cpstate->label_cache_entries) + { + entry = lfirst(lc); + + if (entry->graph_oid == cpstate->graph_oid && + entry->generation == generation && + strcmp(entry->label_name, label_name) == 0) + { + return entry->label_cache; + } + } + + label_cache = search_label_name_graph_cache_cached(label_name, + root_cpstate->graph_oid); + if (label_cache == NULL) + { + return NULL; + } + + entry = palloc(sizeof(*entry)); + entry->label_name = pstrdup(label_name); + entry->graph_oid = root_cpstate->graph_oid; + entry->generation = generation; + entry->label_cache = label_cache; + root_cpstate->label_cache_entries = + lappend(root_cpstate->label_cache_entries, entry); + + return label_cache; +} + +static cypher_parsestate *get_root_cypher_parsestate( + cypher_parsestate *cpstate) +{ + ParseState *pstate = (ParseState *)cpstate; + + while (pstate->parentParseState != NULL) + { + pstate = pstate->parentParseState; + } + + return (cypher_parsestate *)pstate; +} diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index d2b3d7448..bca608b9c 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -138,6 +138,13 @@ typedef struct graph_label_entry Oid relation; } graph_label_entry; +typedef struct graph_label_list +{ + graph_label_entry *entries; + int count; + int capacity; +} graph_label_list; + /* * GRAPH global context per graph. They are chained together via next. * Be aware that the global pointer will point to the root BUT that @@ -155,6 +162,8 @@ typedef struct GRAPH_global_context CommandId curcid; /* snapshot fallback: command id */ int64 num_loaded_vertices; /* number of loaded vertices in this graph */ int64 num_loaded_edges; /* number of loaded edges in this graph */ + graph_label_list vertex_labels; /* loaded vertex labels for shared names */ + graph_label_list edge_labels; /* loaded edge labels for shared names */ ListGraphId *vertices; /* vertices for vertex hashtable cleanup */ struct GRAPH_global_context *next; /* next graph */ } GRAPH_global_context; @@ -192,23 +201,28 @@ static void get_global_graph_scalar_arg_no_copy(char *funcname, static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void load_vertex_hashtable(GRAPH_global_context *ggctx, - Snapshot snapshot, List *vertex_labels); + Snapshot snapshot, + graph_label_list *vertex_labels); static void load_edge_hashtable(GRAPH_global_context *ggctx, - Snapshot snapshot, List *edge_labels); + Snapshot snapshot, + graph_label_list *edge_labels); static void freeze_GRAPH_global_hashtables(GRAPH_global_context *ggctx); static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, - List **vertex_labels, List **edge_labels); -static void free_ag_label_names(List *labels); + graph_label_list *vertex_labels, + graph_label_list *edge_labels); +static void append_graph_label(graph_label_list *labels, Name label_name, + Oid relation); +static void free_ag_label_names(graph_label_list *labels); static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, graphid end_vertex_id, Oid edge_label_table_oid, - char *edge_label_name); + const char *edge_label_name); static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, char *edge_label_name); + graphid edge_id, const char *edge_label_name); static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, - char *vertex_label_name, + const char *vertex_label_name, ItemPointerData tid); static Datum fetch_entry_properties(Oid relid, ItemPointerData tid, AttrNumber property_attr, @@ -313,7 +327,8 @@ static void create_GRAPH_global_hashtables(GRAPH_global_context *ggctx) /* helper function to get label names for the specified graph */ static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, - List **vertex_labels, List **edge_labels) + graph_label_list *vertex_labels, + graph_label_list *edge_labels) { ScanKeyData scan_key; Relation ag_label; @@ -340,10 +355,11 @@ static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, while (HeapTupleIsValid(tuple = systable_getnext(scan_desc))) { - graph_label_entry *label; bool is_null; Datum datum; char label_type; + Name label_name; + Oid label_relation; datum = heap_getattr(tuple, Anum_ag_label_kind, tupdesc, &is_null); if (is_null) @@ -360,21 +376,20 @@ static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, continue; } - label = palloc(sizeof(*label)); - namestrcpy(&label->name, NameStr(*DatumGetName(datum))); + label_name = DatumGetName(datum); datum = heap_getattr(tuple, Anum_ag_label_relation, tupdesc, &is_null); Assert(!is_null); - label->relation = DatumGetObjectId(datum); + label_relation = DatumGetObjectId(datum); if (label_type == LABEL_TYPE_VERTEX) { - *vertex_labels = lappend(*vertex_labels, label); + append_graph_label(vertex_labels, label_name, label_relation); } else { - *edge_labels = lappend(*edge_labels, label); + append_graph_label(edge_labels, label_name, label_relation); } } } @@ -383,16 +398,40 @@ static void get_ag_labels_names(Snapshot snapshot, Oid graph_oid, table_close(ag_label, AccessShareLock); } -static void free_ag_label_names(List *labels) +static void append_graph_label(graph_label_list *labels, Name label_name, + Oid relation) { - ListCell *lc; + graph_label_entry *label; - foreach(lc, labels) + if (labels->count >= labels->capacity) { - pfree(lfirst(lc)); + int new_capacity = labels->capacity == 0 ? 8 : labels->capacity * 2; + + if (labels->entries == NULL) + { + labels->entries = palloc(sizeof(*labels->entries) * new_capacity); + } + else + { + labels->entries = repalloc(labels->entries, + sizeof(*labels->entries) * + new_capacity); + } + + labels->capacity = new_capacity; } - list_free(labels); + label = &labels->entries[labels->count++]; + namestrcpy(&label->name, NameStr(*label_name)); + label->relation = relation; +} + +static void free_ag_label_names(graph_label_list *labels) +{ + pfree_if_not_null(labels->entries); + labels->entries = NULL; + labels->count = 0; + labels->capacity = 0; } /* @@ -402,7 +441,7 @@ static void free_ag_label_names(List *labels) static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ItemPointerData tid, graphid start_vertex_id, graphid end_vertex_id, Oid edge_label_table_oid, - char *edge_label_name) + const char *edge_label_name) { edge_entry *ee = NULL; bool found = false; @@ -451,7 +490,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, ee->start_vertex_id = start_vertex_id; ee->end_vertex_id = end_vertex_id; ee->edge_label_table_oid = edge_label_table_oid; - ee->edge_label_name = pstrdup(edge_label_name); + ee->edge_label_name = (char *)edge_label_name; /* increment the number of loaded edges */ ggctx->num_loaded_edges++; @@ -465,7 +504,7 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, */ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, - char *vertex_label_name, + const char *vertex_label_name, ItemPointerData tid) { vertex_entry *ve = NULL; @@ -508,7 +547,7 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, ve->vertex_id = vertex_id; /* set the label table oid for this vertex */ ve->vertex_label_table_oid = vertex_label_table_oid; - ve->vertex_label_name = pstrdup(vertex_label_name); + ve->vertex_label_name = (char *)vertex_label_name; /* set the TID for lazy property fetch */ ve->tid = tid; /* set the NIL edge list */ @@ -531,7 +570,7 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, */ static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, char *edge_label_name) + graphid edge_id, const char *edge_label_name) { vertex_entry *value = NULL; bool start_found = false; @@ -612,12 +651,13 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, /* helper routine to load all vertices into the GRAPH global vertex hashtable */ static void load_vertex_hashtable(GRAPH_global_context *ggctx, - Snapshot snapshot, List *vertex_labels) + Snapshot snapshot, + graph_label_list *vertex_labels) { - ListCell *lc; + int i; /* go through all vertex label tables in list */ - foreach (lc, vertex_labels) + for (i = 0; i < vertex_labels->count; i++) { Relation graph_vertex_label; TableScanDesc scan_desc; @@ -627,7 +667,7 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx, Oid vertex_label_table_oid; TupleDesc tupdesc; - label_entry = lfirst(lc); + label_entry = &vertex_labels->entries[i]; vertex_label_name = NameStr(label_entry->name); vertex_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ @@ -650,13 +690,6 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx, bool isnull; bool inserted = false; - /* something is wrong if this isn't true */ - if (!HeapTupleIsValid(tuple)) - { - elog(ERROR, "load_vertex_hashtable: !HeapTupleIsValid"); - } - Assert(HeapTupleIsValid(tuple)); - vertex_id = DatumGetInt64(heap_getattr(tuple, Anum_ag_label_vertex_table_id, tupdesc, &isnull)); @@ -696,24 +729,19 @@ static void load_vertex_hashtable(GRAPH_global_context *ggctx, static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx) { Snapshot snapshot = GetActiveSnapshot(); - List *vertex_labels = NIL; - List *edge_labels = NIL; /* initialize statistics */ ggctx->num_loaded_vertices = 0; ggctx->num_loaded_edges = 0; - get_ag_labels_names(snapshot, ggctx->graph_oid, &vertex_labels, - &edge_labels); + get_ag_labels_names(snapshot, ggctx->graph_oid, &ggctx->vertex_labels, + &ggctx->edge_labels); /* insert all of our vertices */ - load_vertex_hashtable(ggctx, snapshot, vertex_labels); + load_vertex_hashtable(ggctx, snapshot, &ggctx->vertex_labels); /* insert all of our edges */ - load_edge_hashtable(ggctx, snapshot, edge_labels); - - free_ag_label_names(vertex_labels); - free_ag_label_names(edge_labels); + load_edge_hashtable(ggctx, snapshot, &ggctx->edge_labels); } /* @@ -721,12 +749,13 @@ static void load_GRAPH_global_hashtables(GRAPH_global_context *ggctx) * hashtables. */ static void load_edge_hashtable(GRAPH_global_context *ggctx, - Snapshot snapshot, List *edge_labels) + Snapshot snapshot, + graph_label_list *edge_labels) { - ListCell *lc; + int i; /* go through all edge label tables in list */ - foreach (lc, edge_labels) + for (i = 0; i < edge_labels->count; i++) { Relation graph_edge_label; TableScanDesc scan_desc; @@ -736,7 +765,7 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx, Oid edge_label_table_oid; TupleDesc tupdesc; - label_entry = lfirst(lc); + label_entry = &edge_labels->entries[i]; edge_label_name = NameStr(label_entry->name); edge_label_table_oid = label_entry->relation; /* open the relation (table) and begin the scan */ @@ -761,13 +790,6 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx, bool isnull; bool inserted = false; - /* something is wrong if this isn't true */ - if (!HeapTupleIsValid(tuple)) - { - elog(ERROR, "load_edge_hashtable: !HeapTupleIsValid"); - } - Assert(HeapTupleIsValid(tuple)); - edge_id = DatumGetInt64(heap_getattr(tuple, Anum_ag_label_edge_table_id, tupdesc, &isnull)); @@ -812,6 +834,7 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx, ereport(WARNING, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("ignored duplicate edge"))); + continue; } /* insert the edge into the start and end vertices edge lists */ @@ -906,6 +929,9 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) free_ListGraphId(ggctx->vertices); ggctx->vertices = NULL; + free_ag_label_names(&ggctx->vertex_labels); + free_ag_label_names(&ggctx->edge_labels); + /* free the hashtables */ hash_destroy(ggctx->vertex_hashtable); hash_destroy(ggctx->edge_hashtable); @@ -1037,6 +1063,7 @@ static GRAPH_global_context *manage_GRAPH_global_contexts_internal( /* otherwise, we need to create one and possibly attach it */ new_ggctx = palloc(sizeof(GRAPH_global_context)); + MemSet(new_ggctx, 0, sizeof(GRAPH_global_context)); if (global_graph_contexts_container.contexts != NULL) { diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 116140d3d..1abe52c42 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -184,6 +184,9 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); static Oid get_cached_vle_graph_oid(const char *graph_name, int graph_name_len); +static Oid get_cached_vle_label_relation(Oid graph_oid, + const char *label_name, + int label_name_len); static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx); static void free_VLE_local_context(VLE_local_context *vlelctx); /* VLE graph traversal functions */ @@ -1019,34 +1022,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, if (agtv_object->type == AGTV_STRING && agtv_object->val.string.len != 0) { - label_cache_data *label_cache; - char label_name_buf[NAMEDATALEN]; - char *label_name; - bool free_label_name = false; - - if (agtv_object->val.string.len < NAMEDATALEN) - { - memcpy(label_name_buf, agtv_object->val.string.val, - agtv_object->val.string.len); - label_name_buf[agtv_object->val.string.len] = '\0'; - label_name = label_name_buf; - } - else - { - label_name = pnstrdup(agtv_object->val.string.val, - agtv_object->val.string.len); - free_label_name = true; - } - - label_cache = search_label_name_graph_cache_cached( - label_name, graph_oid); - vlelctx->edge_label_name_oid = label_cache != NULL ? - label_cache->relation : InvalidOid; + vlelctx->edge_label_name_oid = get_cached_vle_label_relation( + graph_oid, agtv_object->val.string.val, + agtv_object->val.string.len); vlelctx->edge_label_name = NULL; - if (free_label_name) - { - pfree(label_name); - } } else { @@ -1193,6 +1172,64 @@ static Oid get_cached_vle_graph_oid(const char *graph_name, return cached_graph_oid; } +static Oid get_cached_vle_label_relation(Oid graph_oid, + const char *label_name, + int label_name_len) +{ + static NameData cached_label_name; + static Oid cached_graph_oid = InvalidOid; + static Oid cached_relation_oid = InvalidOid; + static uint64 cached_generation = 0; + uint64 current_generation = get_label_cache_generation(); + char *label_name_cstr; + NameData label_name_buf; + bool free_label_name = false; + label_cache_data *label_cache; + + if (OidIsValid(cached_relation_oid) && + cached_graph_oid == graph_oid && + cached_generation == current_generation && + label_name_len < NAMEDATALEN && + strncmp(NameStr(cached_label_name), label_name, label_name_len) == 0 && + NameStr(cached_label_name)[label_name_len] == '\0') + { + return cached_relation_oid; + } + + if (label_name_len < NAMEDATALEN) + { + memcpy(NameStr(label_name_buf), label_name, label_name_len); + NameStr(label_name_buf)[label_name_len] = '\0'; + label_name_cstr = NameStr(label_name_buf); + } + else + { + label_name_cstr = pnstrdup(label_name, label_name_len); + free_label_name = true; + } + + label_cache = search_label_name_graph_cache_cached(label_name_cstr, + graph_oid); + cached_graph_oid = graph_oid; + cached_relation_oid = label_cache != NULL ? + label_cache->relation : InvalidOid; + cached_generation = current_generation; + if (label_name_len < NAMEDATALEN) + { + cached_label_name = label_name_buf; + } + else + { + namestrcpy(&cached_label_name, label_name_cstr); + } + if (free_label_name) + { + pfree(label_name_cstr); + } + + return cached_relation_oid; +} + /* * Helper function to get the specified edge's state. If it does not find it, it * creates and initializes it. diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 613697b4f..30849e54a 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -6314,8 +6314,22 @@ static char *get_label_name(const char *graph_name, int graph_name_len, { Oid graph_oid = get_cached_graph_oid_for_name(graph_name, graph_name_len); int32 label_id = get_graphid_label_id(element_graphid); + static Oid cached_graph_oid = InvalidOid; + static int32 cached_label_id = -1; + static uint64 cached_generation = 0; + static NameData cached_label_name; + static Oid cached_label_relation = InvalidOid; + uint64 current_generation = get_label_cache_generation(); label_cache_data *label_cache; + if (cached_graph_oid == graph_oid && + cached_label_id == label_id && + cached_generation == current_generation) + { + *label_relation = cached_label_relation; + return NameStr(cached_label_name); + } + label_cache = search_label_graph_oid_cache_cached(graph_oid, label_id); if (label_cache == NULL) @@ -6326,8 +6340,13 @@ static char *get_label_name(const char *graph_name, int graph_name_len, } *label_relation = label_cache->relation; + cached_graph_oid = graph_oid; + cached_label_id = label_id; + cached_generation = current_generation; + namestrcpy(&cached_label_name, NameStr(label_cache->name)); + cached_label_relation = label_cache->relation; - return NameStr(label_cache->name); + return NameStr(cached_label_name); } static Oid get_cached_graph_oid_for_name(const char *graph_name, diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index 5efa4c215..e669d3908 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -31,8 +31,8 @@ #include "catalog/ag_label.h" #include "utils/ag_cache.h" -#define LAST_GRAPH_CACHE_SIZE 4 -#define LAST_LABEL_CACHE_SIZE 4 +#define LAST_GRAPH_CACHE_SIZE 8 +#define LAST_LABEL_CACHE_SIZE 8 typedef struct graph_name_cache_entry { @@ -343,11 +343,19 @@ uint64 get_graph_cache_generation(void) return graph_cache_generation; } +void invalidate_graph_cache(void) +{ + initialize_caches(); + + graph_cache_generation++; +} + graph_cache_data *search_graph_name_cache_cached(const char *name) { static NameData cached_names[LAST_GRAPH_CACHE_SIZE]; static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_GRAPH_CACHE_SIZE] = {false}; static int next_slot = 0; uint64 current_generation = get_graph_cache_generation(); int slot; @@ -357,7 +365,7 @@ graph_cache_data *search_graph_name_cache_cached(const char *name) for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) { - if (cached_graphs[i] != NULL && + if (cached_valid[i] && cached_generations[i] == current_generation && namestrcmp(&cached_names[i], name) == 0) { @@ -368,7 +376,7 @@ graph_cache_data *search_graph_name_cache_cached(const char *name) slot = -1; for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) { - if (cached_graphs[i] == NULL || + if (!cached_valid[i] || cached_generations[i] != current_generation) { slot = i; @@ -383,11 +391,9 @@ graph_cache_data *search_graph_name_cache_cached(const char *name) } cached_graphs[slot] = search_graph_name_cache(name); - if (cached_graphs[slot] != NULL) - { - namestrcpy(&cached_names[slot], name); - cached_generations[slot] = current_generation; - } + namestrcpy(&cached_names[slot], name); + cached_generations[slot] = current_generation; + cached_valid[slot] = true; return cached_graphs[slot]; } @@ -397,6 +403,7 @@ graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) static Oid cached_namespaces[LAST_GRAPH_CACHE_SIZE] = {InvalidOid}; static uint64 cached_generations[LAST_GRAPH_CACHE_SIZE] = {0}; static graph_cache_data *cached_graphs[LAST_GRAPH_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_GRAPH_CACHE_SIZE] = {false}; static int next_slot = 0; uint64 current_generation = get_graph_cache_generation(); int slot; @@ -404,7 +411,7 @@ graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) { - if (cached_graphs[i] != NULL && + if (cached_valid[i] && cached_generations[i] == current_generation && cached_namespaces[i] == namespace) { @@ -415,7 +422,7 @@ graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) slot = -1; for (i = 0; i < LAST_GRAPH_CACHE_SIZE; i++) { - if (cached_graphs[i] == NULL || + if (!cached_valid[i] || cached_generations[i] != current_generation) { slot = i; @@ -430,11 +437,9 @@ graph_cache_data *search_graph_namespace_cache_cached(Oid namespace) } cached_graphs[slot] = search_graph_namespace_cache(namespace); - if (cached_graphs[slot] != NULL) - { - cached_namespaces[slot] = namespace; - cached_generations[slot] = current_generation; - } + cached_namespaces[slot] = namespace; + cached_generations[slot] = current_generation; + cached_valid[slot] = true; return cached_graphs[slot]; } @@ -980,20 +985,30 @@ uint64 get_label_cache_generation(void) return label_cache_generation; } +void invalidate_label_cache(void) +{ + initialize_caches(); + + label_cache_generation++; + label_seq_relation_cache_generation++; +} + label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) { static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; static int32 cached_ids[LAST_LABEL_CACHE_SIZE] = {-1, -1, -1, -1}; static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; static int next_slot = 0; uint64 current_generation = get_label_cache_generation(); label_cache_data *label; + int slot; int i; for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - if (cached_labels[i] != NULL && + if (cached_valid[i] && cached_graphs[i] == graph && cached_ids[i] == id && cached_generations[i] == current_generation) @@ -1003,32 +1018,29 @@ label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id) } label = search_label_graph_oid_cache(graph, id); - if (label != NULL) + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - int slot = -1; - - for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) - { - if (cached_labels[i] == NULL || - cached_generations[i] != current_generation) - { - slot = i; - break; - } - } - - if (slot < 0) + if (!cached_valid[i] || + cached_generations[i] != current_generation) { - slot = next_slot; - next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + slot = i; + break; } + } - cached_graphs[slot] = graph; - cached_ids[slot] = id; - cached_generations[slot] = current_generation; - cached_labels[slot] = label; + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; } + cached_graphs[slot] = graph; + cached_ids[slot] = id; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + return label; } @@ -1039,16 +1051,18 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, static Oid cached_graphs[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; static int next_slot = 0; uint64 current_generation = get_label_cache_generation(); label_cache_data *label; + int slot; int i; Assert(name); for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - if (cached_labels[i] != NULL && + if (cached_valid[i] && cached_graphs[i] == graph && cached_generations[i] == current_generation && namestrcmp(&cached_names[i], name) == 0) @@ -1058,32 +1072,29 @@ label_cache_data *search_label_name_graph_cache_cached(const char *name, } label = search_label_name_graph_cache(name, graph); - if (label != NULL) + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - int slot = -1; - - for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) - { - if (cached_labels[i] == NULL || - cached_generations[i] != current_generation) - { - slot = i; - break; - } - } - - if (slot < 0) + if (!cached_valid[i] || + cached_generations[i] != current_generation) { - slot = next_slot; - next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + slot = i; + break; } + } - namestrcpy(&cached_names[slot], name); - cached_graphs[slot] = graph; - cached_generations[slot] = current_generation; - cached_labels[slot] = label; + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; } + namestrcpy(&cached_names[slot], name); + cached_graphs[slot] = graph; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + return label; } @@ -1267,14 +1278,16 @@ label_cache_data *search_label_relation_cache_cached(Oid relation) static Oid cached_relations[LAST_LABEL_CACHE_SIZE] = {InvalidOid}; static uint64 cached_generations[LAST_LABEL_CACHE_SIZE] = {0}; static label_cache_data *cached_labels[LAST_LABEL_CACHE_SIZE] = {NULL}; + static bool cached_valid[LAST_LABEL_CACHE_SIZE] = {false}; static int next_slot = 0; uint64 current_generation = get_label_cache_generation(); label_cache_data *label; + int slot; int i; for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - if (cached_labels[i] != NULL && + if (cached_valid[i] && cached_relations[i] == relation && cached_generations[i] == current_generation) { @@ -1283,31 +1296,28 @@ label_cache_data *search_label_relation_cache_cached(Oid relation) } label = search_label_relation_cache(relation); - if (label != NULL) + slot = -1; + for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) { - int slot = -1; - - for (i = 0; i < LAST_LABEL_CACHE_SIZE; i++) - { - if (cached_labels[i] == NULL || - cached_generations[i] != current_generation) - { - slot = i; - break; - } - } - - if (slot < 0) + if (!cached_valid[i] || + cached_generations[i] != current_generation) { - slot = next_slot; - next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; + slot = i; + break; } + } - cached_relations[slot] = relation; - cached_generations[slot] = current_generation; - cached_labels[slot] = label; + if (slot < 0) + { + slot = next_slot; + next_slot = (next_slot + 1) % LAST_LABEL_CACHE_SIZE; } + cached_relations[slot] = relation; + cached_generations[slot] = current_generation; + cached_labels[slot] = label; + cached_valid[slot] = true; + return label; } diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index 784e1e882..29056518b 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -22,6 +22,7 @@ #include "access/genam.h" #include "access/table.h" #include "commands/graph_commands.h" +#include "nodes/makefuncs.h" #include "utils/load/age_load.h" @@ -209,11 +210,12 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) graph_oid); if (vertex_cache == NULL) { - DirectFunctionCall2(create_vlabel, - CStringGetDatum(graph_name->data), - CStringGetDatum(vtx_name_str)); - vertex_cache = search_label_name_graph_cache_cached(vtx_name_str, - graph_oid); + List *parent = list_make1(makeRangeVar( + graph_name_str, pstrdup(AG_DEFAULT_LABEL_VERTEX), -1)); + Oid label_relid = create_label(graph_name_str, vtx_name_str, + LABEL_TYPE_VERTEX, parent); + + vertex_cache = search_label_relation_cache_cached(label_relid); if (vertex_cache == NULL) { ereport(ERROR, @@ -221,16 +223,19 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) errmsg("vertex label \"%s\" was not found after creation", vtx_name_str))); } + ereport(NOTICE, + (errmsg("VLabel \"%s\" has been created", vtx_name_str))); } edge_cache = search_label_name_graph_cache_cached(edge_name_str, graph_oid); if (edge_cache == NULL) { - DirectFunctionCall2(create_elabel, - CStringGetDatum(graph_name->data), - CStringGetDatum(edge_label_name->data)); - edge_cache = search_label_name_graph_cache_cached(edge_name_str, - graph_oid); + List *parent = list_make1(makeRangeVar( + graph_name_str, pstrdup(AG_DEFAULT_LABEL_EDGE), -1)); + Oid label_relid = create_label(graph_name_str, edge_name_str, + LABEL_TYPE_EDGE, parent); + + edge_cache = search_label_relation_cache_cached(label_relid); if (edge_cache == NULL) { ereport(ERROR, @@ -238,6 +243,8 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) errmsg("edge label \"%s\" was not found after creation", edge_name_str))); } + ereport(NOTICE, + (errmsg("ELabel \"%s\" has been created", edge_name_str))); } vtx_label_id = vertex_cache->id; diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index 7f35a22ad..a6de27359 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -27,6 +27,7 @@ #include "catalog/pg_authid.h" #include "executor/executor.h" #include "miscadmin.h" +#include "nodes/makefuncs.h" #include "nodes/parsenodes.h" #include "parser/parse_relation.h" #include "utils/acl.h" @@ -732,22 +733,29 @@ Datum load_edges_from_file(PG_FUNCTION_ARGS) */ static Oid get_or_create_graph(const Name graph_name) { - Oid graph_oid; + graph_cache_data *graph_cache; char *graph_name_str; graph_name_str = NameStr(*graph_name); - graph_oid = get_graph_oid(graph_name_str); - - if (OidIsValid(graph_oid)) + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache != NULL) { - return graph_oid; + return graph_cache->oid; } - graph_oid = create_graph_internal(graph_name); + create_graph_internal(graph_name); + graph_cache = search_graph_name_cache_cached(graph_name_str); + if (graph_cache == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" was not found after creation", + graph_name_str))); + } ereport(NOTICE, (errmsg("graph \"%s\" has been created", NameStr(*graph_name)))); - - return graph_oid; + + return graph_cache->oid; } /* @@ -784,14 +792,14 @@ static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, /* Create a label */ RangeVar *rv; List *parent; + Oid label_relid; char *default_label = (label_kind == LABEL_KIND_VERTEX) ? AG_DEFAULT_LABEL_VERTEX : AG_DEFAULT_LABEL_EDGE; - rv = get_label_range_var(graph_name, graph_oid, default_label); + rv = makeRangeVar(graph_name, pstrdup(default_label), -1); parent = list_make1(rv); - create_label(graph_name, label_name, label_kind, parent); - label_cache = search_label_name_graph_cache_cached(label_name, - graph_oid); + label_relid = create_label(graph_name, label_name, label_kind, parent); + label_cache = search_label_relation_cache_cached(label_relid); if (label_cache == NULL) { ereport(ERROR, diff --git a/src/include/commands/label_commands.h b/src/include/commands/label_commands.h index cfbbdb336..9baf84526 100644 --- a/src/include/commands/label_commands.h +++ b/src/include/commands/label_commands.h @@ -52,8 +52,8 @@ #define IS_AG_DEFAULT_LABEL(x) \ (IS_DEFAULT_LABEL_EDGE(x) || IS_DEFAULT_LABEL_VERTEX(x)) -void create_label(char *graph_name, char *label_name, char label_type, - List *parents); +Oid create_label(char *graph_name, char *label_name, char label_type, + List *parents); Datum create_vlabel(PG_FUNCTION_ARGS); diff --git a/src/include/executor/cypher_utils.h b/src/include/executor/cypher_utils.h index 0ca66796a..fbea8b6b1 100644 --- a/src/include/executor/cypher_utils.h +++ b/src/include/executor/cypher_utils.h @@ -58,6 +58,7 @@ typedef struct cypher_create_custom_scan_state TupleTableSlot *slot; Oid graph_oid; HTAB *entity_exists_index_cache; + HTAB *entity_exists_label_relation_cache; HTAB *result_rel_info_cache; bool graph_mutated; } cypher_create_custom_scan_state; @@ -71,6 +72,7 @@ typedef struct cypher_set_custom_scan_state HTAB *qual_cache; HTAB *index_cache; HTAB *result_rel_info_cache; + HTAB *label_relation_cache; Oid graph_oid; int flags; int *path_update_attrs; @@ -106,6 +108,7 @@ typedef struct cypher_delete_custom_scan_state HTAB *qual_cache; HTAB *index_cache; HTAB *result_rel_info_cache; + HTAB *label_relation_cache; bool graph_mutated; } cypher_delete_custom_scan_state; @@ -139,7 +142,9 @@ typedef struct cypher_merge_custom_scan_state HTAB *update_qual_cache; HTAB *update_index_cache; HTAB *update_result_rel_info_cache; + HTAB *update_label_relation_cache; HTAB *entity_exists_index_cache; + HTAB *entity_exists_label_relation_cache; HTAB *result_rel_info_cache; } cypher_merge_custom_scan_state; @@ -162,12 +167,19 @@ ResultRelInfo *get_entity_result_rel_info(EState *estate, Oid relid); void destroy_entity_result_rel_info_cache(HTAB *result_rel_info_cache); +HTAB *create_label_relation_cache(const char *name); +bool get_label_relation_from_cache(HTAB *label_relation_cache, Oid graph_oid, + int32 label_id, Oid *relation, + char **label_name); +void destroy_label_relation_cache(HTAB *label_relation_cache); + HTAB *create_entity_exists_index_cache(const char *name); void destroy_entity_exists_index_cache(HTAB *index_cache); void destroy_index_cache(HTAB *index_cache, bool close_relations); bool entity_exists(EState *estate, Oid graph_oid, graphid id); bool entity_exists_with_cache(EState *estate, Oid graph_oid, graphid id, - HTAB *index_cache); + HTAB *index_cache, + HTAB *label_relation_cache); HeapTuple insert_entity_tuple(ResultRelInfo *resultRelInfo, TupleTableSlot *elemTupleSlot, EState *estate); diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index 263ea197b..f4f4fd8ba 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -21,6 +21,7 @@ #define AG_CYPHER_PARSE_NODE_H #include "nodes/cypher_nodes.h" +#include "utils/ag_cache.h" /* * Every internal alias or variable name should be prefixed @@ -39,6 +40,7 @@ typedef struct cypher_parsestate Param *params; int default_alias_num; List *entities; + List *label_cache_entries; List *property_constraint_quals; bool subquery_where_flag; /* flag for knowing we are in a subquery where */ /* @@ -67,5 +69,7 @@ void setup_errpos_ecb(errpos_ecb_state *ecb_state, ParseState *pstate, int query_loc); void cancel_errpos_ecb(errpos_ecb_state *ecb_state); char *get_next_default_alias(cypher_parsestate *cpstate); +label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, + const char *label_name); #endif diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index 49826741c..b61e602ea 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -46,7 +46,9 @@ graph_cache_data *search_graph_name_cache_cached(const char *name); graph_cache_data *search_graph_namespace_cache(Oid namespace); graph_cache_data *search_graph_namespace_cache_cached(Oid namespace); uint64 get_graph_cache_generation(void); +void invalidate_graph_cache(void); uint64 get_label_cache_generation(void); +void invalidate_label_cache(void); label_cache_data *search_label_oid_cache(Oid oid); label_cache_data *search_label_name_graph_cache(const char *name, Oid graph); label_cache_data *search_label_name_graph_cache_cached(const char *name, From 6eb4f648c585b1d983432b6e37714001bc36ef52 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:39 +0900 Subject: [PATCH 14/15] Use cached graph names in DDL paths Carry cached graph and label names through graph generation, label DDL, object access errors, and graph drop paths. This avoids SQL wrapper calls and default label lookups when graph OIDs and label relids are already known, creates labels with the known graph OID, uses cached relation names when dropping labels, and reuses graph-cache names for namespace and DDL work. --- src/backend/catalog/ag_catalog.c | 10 +- src/backend/catalog/ag_graph.c | 12 +- src/backend/catalog/ag_label.c | 30 +++- src/backend/commands/graph_commands.c | 32 ++-- src/backend/commands/label_commands.c | 64 +++++--- src/backend/executor/cypher_create.c | 3 +- src/backend/executor/cypher_delete.c | 5 +- src/backend/executor/cypher_merge.c | 11 +- src/backend/executor/cypher_set.c | 6 +- src/backend/nodes/cypher_copyfuncs.c | 7 +- src/backend/nodes/cypher_outfuncs.c | 6 + src/backend/nodes/cypher_readfuncs.c | 7 +- src/backend/parser/cypher_clause.c | 38 ++++- src/backend/parser/cypher_parse_node.c | 16 ++ src/backend/utils/adt/agtype.c | 218 ++++++++++++++++--------- src/backend/utils/cache/ag_cache.c | 31 +++- src/backend/utils/graph_generation.c | 116 +++++++------ src/backend/utils/load/age_load.c | 8 +- src/include/commands/label_commands.h | 3 + src/include/nodes/cypher_nodes.h | 5 + src/include/parser/cypher_parse_node.h | 4 + src/include/utils/ag_cache.h | 1 + src/include/utils/agtype.h | 1 + 23 files changed, 421 insertions(+), 213 deletions(-) diff --git a/src/backend/catalog/ag_catalog.c b/src/backend/catalog/ag_catalog.c index 6c7e70562..0bdfeeb38 100644 --- a/src/backend/catalog/ag_catalog.c +++ b/src/backend/catalog/ag_catalog.c @@ -254,11 +254,10 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, cache_data = search_graph_namespace_cache_cached(object_id); if (cache_data) { - char *nspname = get_namespace_name(object_id); - ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("schema \"%s\" is for graph \"%s\"", - nspname, NameStr(cache_data->name)))); + NameStr(cache_data->name), + NameStr(cache_data->name)))); } return; @@ -285,11 +284,10 @@ static void object_access(ObjectAccessType access, Oid class_id, Oid object_id, } else { - char *relname = get_rel_name(object_id); - ereport(ERROR, (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), errmsg("table \"%s\" is for label \"%s\"", - relname, NameStr(cache_data->name)))); + get_label_cache_relation_name(cache_data), + NameStr(cache_data->name)))); } } } diff --git a/src/backend/catalog/ag_graph.c b/src/backend/catalog/ag_graph.c index c77af98a6..bffedb3f7 100644 --- a/src/backend/catalog/ag_graph.c +++ b/src/backend/catalog/ag_graph.c @@ -22,13 +22,10 @@ #include "access/genam.h" #include "access/heapam.h" #include "catalog/indexing.h" -#include "utils/lsyscache.h" #include "catalog/ag_graph.h" #include "utils/ag_cache.h" -static Oid get_graph_namespace(const char *graph_name); - /* INSERT INTO ag_catalog.ag_graph VALUES (graph_name, nsp_id) */ void insert_graph(const Name graph_name, const Oid nsp_id) { @@ -161,7 +158,7 @@ Oid get_graph_oid(const char *graph_name) } } -static Oid get_graph_namespace(const char *graph_name) +char *get_graph_namespace_name(const char *graph_name) { graph_cache_data *cache_data; @@ -172,12 +169,7 @@ static Oid get_graph_namespace(const char *graph_name) errmsg("graph \"%s\" does not exist", graph_name))); } - return cache_data->namespace; -} - -char *get_graph_namespace_name(const char *graph_name) -{ - return get_namespace_name(get_graph_namespace(graph_name)); + return pstrdup(NameStr(cache_data->name)); } bool graph_namespace_exists(Oid graph_oid) diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index dca0ebf0e..02d0f703c 100644 --- a/src/backend/catalog/ag_label.c +++ b/src/backend/catalog/ag_label.c @@ -154,7 +154,7 @@ char *get_label_relation_name(const char *label_name, Oid graph_oid) cache_data = search_label_name_graph_cache_cached(label_name, graph_oid); if (cache_data) - return pstrdup(NameStr(cache_data->relation_name)); + return pstrdup(get_label_cache_relation_name(cache_data)); else return NULL; } @@ -226,6 +226,14 @@ Datum _label_id(PG_FUNCTION_ARGS) Name label_name; Oid graph; int32 id; + static NameData cached_graph_name; + static NameData cached_label_name; + static int32 cached_label_id = INVALID_LABEL_ID; + static uint64 cached_graph_generation = 0; + static uint64 cached_label_generation = 0; + static bool cached_valid = false; + uint64 graph_generation; + uint64 label_generation; if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) { @@ -235,9 +243,27 @@ Datum _label_id(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); label_name = PG_GETARG_NAME(1); + graph_generation = get_graph_cache_generation(); + label_generation = get_label_cache_generation(); + if (cached_valid && + cached_graph_generation == graph_generation && + cached_label_generation == label_generation && + namestrcmp(&cached_graph_name, NameStr(*graph_name)) == 0 && + namestrcmp(&cached_label_name, NameStr(*label_name)) == 0) + { + PG_RETURN_INT32(cached_label_id); + } + graph = get_graph_oid(NameStr(*graph_name)); id = get_label_id(NameStr(*label_name), graph); + cached_graph_name = *graph_name; + cached_label_name = *label_name; + cached_label_id = id; + cached_graph_generation = graph_generation; + cached_label_generation = label_generation; + cached_valid = true; + PG_RETURN_INT32(id); } @@ -279,7 +305,7 @@ RangeVar *get_label_range_var(char *graph_name, Oid graph_oid, label_cache = search_label_name_graph_cache_cached(label_name, graph_oid); - relname = pstrdup(NameStr(label_cache->relation_name)); + relname = pstrdup(get_label_cache_relation_name(label_cache)); return makeRangeVar(graph_name, relname, 2); } diff --git a/src/backend/commands/graph_commands.c b/src/backend/commands/graph_commands.c index f7e8d070b..5fd44a1c5 100644 --- a/src/backend/commands/graph_commands.c +++ b/src/backend/commands/graph_commands.c @@ -26,6 +26,7 @@ #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parser.h" +#include "utils/builtins.h" #include "catalog/ag_graph.h" #include "catalog/ag_label.h" @@ -40,6 +41,7 @@ #define gen_graph_namespace_name(graph_name) (graph_name) static Oid create_schema_for_graph(const Name graph_name); +static void drop_graph_internal(const char *graph_name_str, const bool cascade); static void drop_schema_for_graph(char *graph_name_str, const bool cascade); static void remove_schema(Node *schema_name, DropBehavior behavior); static void rename_graph(const Name graph_name, const Name new_name); @@ -104,8 +106,10 @@ Oid create_graph_internal(const Name graph_name) CommandCounterIncrement(); /* Create the default label tables */ - create_label(graph_name_str, AG_DEFAULT_LABEL_VERTEX, LABEL_TYPE_VERTEX, NIL); - create_label(graph_name_str, AG_DEFAULT_LABEL_EDGE, LABEL_TYPE_EDGE, NIL); + create_label_with_graph_oid(graph_name_str, nsp_id, AG_DEFAULT_LABEL_VERTEX, + LABEL_TYPE_VERTEX, NIL); + create_label_with_graph_oid(graph_name_str, nsp_id, AG_DEFAULT_LABEL_EDGE, + LABEL_TYPE_EDGE, NIL); return nsp_id; } @@ -192,7 +196,6 @@ PG_FUNCTION_INFO_V1(drop_graph); Datum drop_graph(PG_FUNCTION_ARGS) { Name graph_name; - char *graph_name_str; bool cascade; if (PG_ARGISNULL(0)) @@ -203,21 +206,28 @@ Datum drop_graph(PG_FUNCTION_ARGS) graph_name = PG_GETARG_NAME(0); cascade = PG_GETARG_BOOL(1); - graph_name_str = NameStr(*graph_name); + drop_graph_internal(NameStr(*graph_name), cascade); + + PG_RETURN_VOID(); +} + +static void drop_graph_internal(const char *graph_name_str, const bool cascade) +{ + NameData graph_name; + if (!graph_exists(graph_name_str)) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), errmsg("graph \"%s\" does not exist", graph_name_str))); } - drop_schema_for_graph(graph_name_str, cascade); + drop_schema_for_graph((char *)graph_name_str, cascade); - delete_graph(graph_name); + namestrcpy(&graph_name, graph_name_str); + delete_graph(&graph_name); CommandCounterIncrement(); ereport(NOTICE, (errmsg("graph \"%s\" has been dropped", graph_name_str))); - - PG_RETURN_VOID(); } static void drop_schema_for_graph(char *graph_name_str, const bool cascade) @@ -407,7 +417,8 @@ List *get_graphnames(void) slot_getallattrs(slot); - str = DatumGetCString(slot->tts_values[Anum_ag_graph_name - 1]); + str = pstrdup(NameStr(*DatumGetName( + slot->tts_values[Anum_ag_graph_name - 1]))); graphnames = lappend(graphnames, str); } @@ -427,7 +438,6 @@ void drop_graphs(List *graphnames) { char *graphname = lfirst(lc); - DirectFunctionCall2( - drop_graph, CStringGetDatum(graphname), BoolGetDatum(true)); + drop_graph_internal(graphname, true); } } diff --git a/src/backend/commands/label_commands.c b/src/backend/commands/label_commands.c index 06959b758..1e320be56 100644 --- a/src/backend/commands/label_commands.c +++ b/src/backend/commands/label_commands.c @@ -214,7 +214,7 @@ Datum create_vlabel(PG_FUNCTION_ARGS) } /* Create the default label tables */ - rv = get_label_range_var(graph_name, graph_oid, AG_DEFAULT_LABEL_VERTEX); + rv = makeRangeVar(graph_name, pstrdup(AG_DEFAULT_LABEL_VERTEX), -1); parent = list_make1(rv); @@ -298,7 +298,7 @@ Datum create_elabel(PG_FUNCTION_ARGS) } /* Create the default label tables */ - rv = get_label_range_var(graph_name, graph_oid, AG_DEFAULT_LABEL_EDGE); + rv = makeRangeVar(graph_name, pstrdup(AG_DEFAULT_LABEL_EDGE), -1); parent = list_make1(rv); create_label_with_graph_cache(graph_name, label_name, LABEL_TYPE_EDGE, @@ -336,6 +336,33 @@ Oid create_label(char *graph_name, char *label_name, char label_type, parents, cache_data); } +Oid create_label_with_graph_oid(char *graph_name, Oid graph_oid, + char *label_name, char label_type, + List *parents) +{ + graph_cache_data *cache_data; + + if (!is_valid_label_name(label_name, label_type)) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("label name is invalid"))); + } + + cache_data = search_graph_namespace_cache_cached(graph_oid); + if (!cache_data || strcmp(NameStr(cache_data->name), graph_name) != 0) + { + cache_data = search_graph_name_cache_cached(graph_name); + } + if (!cache_data || cache_data->oid != graph_oid) + { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_SCHEMA), + errmsg("graph \"%s\" does not exist", graph_name))); + } + + return create_label_with_graph_cache(graph_name, label_name, label_type, + parents, cache_data); +} + static Oid create_label_with_graph_cache(char *graph_name, char *label_name, char label_type, List *parents, graph_cache_data *cache_data) @@ -352,8 +379,8 @@ static Oid create_label_with_graph_cache(char *graph_name, char *label_name, graph_oid = cache_data->oid; nsp_id = cache_data->namespace; - /* create a sequence for the new label to generate unique IDs for vertices */ - schema_name = get_namespace_name(nsp_id); + /* graph namespace names are kept in sync with graph names */ + schema_name = pstrdup(NameStr(cache_data->name)); rel_name = gen_label_relation_name(label_name); seq_name = ChooseRelationName(rel_name, "id", "seq", nsp_id, false); seq_range_var = makeRangeVar(schema_name, seq_name, -1); @@ -916,10 +943,9 @@ Datum drop_label(PG_FUNCTION_ARGS) bool force; char *graph_name_str; graph_cache_data *cache_data; + label_cache_data *label_cache; Oid graph_oid; - Oid nsp_id; char *label_name_str; - Oid label_relation; char *schema_name; char *rel_name; List *qname; @@ -947,11 +973,11 @@ Datum drop_label(PG_FUNCTION_ARGS) errmsg("graph \"%s\" does not exist", graph_name_str))); } graph_oid = cache_data->oid; - nsp_id = cache_data->namespace; label_name_str = NameStr(*label_name); - label_relation = get_label_relation(label_name_str, graph_oid); - if (!OidIsValid(label_relation)) + label_cache = search_label_name_graph_cache_cached(label_name_str, + graph_oid); + if (label_cache == NULL) { ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), @@ -964,25 +990,9 @@ Datum drop_label(PG_FUNCTION_ARGS) errmsg("force option is not supported yet"))); } - /* validate schema_name */ - schema_name = get_namespace_name(nsp_id); - if (schema_name == NULL) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("schema_name not found for namespace id \"%d\"", - nsp_id))); - } + schema_name = pstrdup(NameStr(cache_data->name)); - /* validate rel_name */ - rel_name = get_rel_name(label_relation); - if (rel_name == NULL) - { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("rel_name not found for label \"%s\"", - label_name_str))); - } + rel_name = pstrdup(get_label_cache_relation_name(label_cache)); /* build qualified name */ qname = list_make2(makeString(schema_name), makeString(rel_name)); diff --git a/src/backend/executor/cypher_create.c b/src/backend/executor/cypher_create.c index 04e3de3b2..354ea5911 100644 --- a/src/backend/executor/cypher_create.c +++ b/src/backend/executor/cypher_create.c @@ -184,7 +184,8 @@ static void process_pattern(cypher_create_custom_scan_state *css) ps = css->css.ss.ps.lefttree; scantuple = ps->ps_ExprContext->ecxt_scantuple; - result = make_path(css->path_values); + result = make_path_with_length(css->path_values, + path->path_length); scantuple->tts_values[path->path_attr_num - 1] = result; scantuple->tts_isnull[path->path_attr_num - 1] = false; diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 3b446cc81..934386254 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -289,7 +289,8 @@ static int *ensure_delete_item_positions(EState *estate, return delete_data->delete_item_positions; } - count = list_length(delete_data->delete_items); + count = delete_data->delete_item_count; + Assert(count == list_length(delete_data->delete_items)); if (delete_data->delete_item_positions != NULL) { @@ -298,8 +299,6 @@ static int *ensure_delete_item_positions(EState *estate, delete_data->delete_item_positions = count > 0 ? MemoryContextAlloc(estate->es_query_cxt, sizeof(int) * count) : NULL; - delete_data->delete_item_count = count; - foreach(lc, delete_data->delete_items) { cypher_delete_item *item = lfirst(lc); diff --git a/src/backend/executor/cypher_merge.c b/src/backend/executor/cypher_merge.c index 92cef8239..1060aedcc 100644 --- a/src/backend/executor/cypher_merge.c +++ b/src/backend/executor/cypher_merge.c @@ -347,7 +347,8 @@ static void process_path(cypher_merge_custom_scan_state *css, (tuple_position < scantuple->tts_tupleDescriptor->natts || scantuple->tts_tupleDescriptor->natts != 1)) { - result = make_path(css->path_values); + result = make_path_with_length(css->path_values, + path->path_length); /* store the result */ scantuple->tts_values[tuple_position] = result; @@ -1350,18 +1351,16 @@ Node *create_cypher_merge_plan_state(CustomScan *cscan) cypher_css->flags = merge_information->flags; cypher_css->merge_function_attr = merge_information->merge_function_attr; cypher_css->path = merge_information->path; - cypher_css->path_length = list_length(cypher_css->path->target_nodes); + cypher_css->path_length = merge_information->path_length; cypher_css->created_new_path = false; cypher_css->found_a_path = false; cypher_css->graph_oid = merge_information->graph_oid; cypher_css->on_match_set_info = merge_information->on_match_set_info; cypher_css->on_create_set_info = merge_information->on_create_set_info; cypher_css->on_match_set_item_count = - cypher_css->on_match_set_info == NULL ? 0 : - list_length(cypher_css->on_match_set_info->set_items); + merge_information->on_match_set_item_count; cypher_css->on_create_set_item_count = - cypher_css->on_create_set_info == NULL ? 0 : - list_length(cypher_css->on_create_set_info->set_items); + merge_information->on_create_set_item_count; cypher_css->css.ss.ps.type = T_CustomScanState; cypher_css->css.methods = &cypher_merge_exec_methods; diff --git a/src/backend/executor/cypher_set.c b/src/backend/executor/cypher_set.c index bbe8ba691..f5786abd3 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -116,7 +116,8 @@ static void begin_cypher_set(CustomScanState *node, EState *estate, estate->es_output_cid = estate->es_snapshot->curcid; } - css->set_item_count = list_length(css->set_list->set_items); + css->set_item_count = css->set_list->set_item_count; + Assert(css->set_item_count == list_length(css->set_list->set_items)); foreach(lc, css->set_list->set_items) { @@ -652,7 +653,8 @@ bool apply_update_list(CustomScanState *node, } if (num_set_items <= 0) - num_set_items = list_length(set_info->set_items); + num_set_items = set_info->set_item_count; + Assert(num_set_items == list_length(set_info->set_items)); if (num_set_items > 1) { diff --git a/src/backend/nodes/cypher_copyfuncs.c b/src/backend/nodes/cypher_copyfuncs.c index e40c5b40e..f14e0d808 100644 --- a/src/backend/nodes/cypher_copyfuncs.c +++ b/src/backend/nodes/cypher_copyfuncs.c @@ -86,6 +86,7 @@ void copy_cypher_create_path(ExtensibleNode *newnode, const ExtensibleNode *from COPY_SCALAR_FIELD(path_attr_num); COPY_STRING_FIELD(var_name); + COPY_SCALAR_FIELD(path_length); COPY_NODE_FIELD(target_nodes); } @@ -123,6 +124,7 @@ void copy_cypher_update_information(ExtensibleNode *newnode, const ExtensibleNod COPY_STRING_FIELD(graph_name); COPY_SCALAR_FIELD(graph_oid); COPY_STRING_FIELD(clause_name); + COPY_SCALAR_FIELD(set_item_count); extended_newnode->last_update_indexes = NULL; extended_newnode->last_update_index_count = 0; @@ -155,9 +157,9 @@ void copy_cypher_delete_information(ExtensibleNode *newnode, const ExtensibleNod COPY_STRING_FIELD(graph_name); COPY_SCALAR_FIELD(graph_oid); COPY_SCALAR_FIELD(detach); + COPY_SCALAR_FIELD(delete_item_count); extended_newnode->delete_item_positions = NULL; - extended_newnode->delete_item_count = 0; extended_newnode->delete_item_positions_valid = false; } @@ -179,8 +181,11 @@ void copy_cypher_merge_information(ExtensibleNode *newnode, const ExtensibleNode COPY_SCALAR_FIELD(graph_oid); COPY_SCALAR_FIELD(merge_function_attr); COPY_NODE_FIELD(path); + COPY_SCALAR_FIELD(path_length); COPY_NODE_FIELD(on_match_set_info); COPY_NODE_FIELD(on_create_set_info); + COPY_SCALAR_FIELD(on_match_set_item_count); + COPY_SCALAR_FIELD(on_create_set_item_count); } /* copy function for cypher_predicate_function */ diff --git a/src/backend/nodes/cypher_outfuncs.c b/src/backend/nodes/cypher_outfuncs.c index 51e06bc5d..7348433ff 100644 --- a/src/backend/nodes/cypher_outfuncs.c +++ b/src/backend/nodes/cypher_outfuncs.c @@ -393,6 +393,7 @@ void out_cypher_create_path(StringInfo str, const ExtensibleNode *node) WRITE_NODE_FIELD(target_nodes); WRITE_INT32_FIELD(path_attr_num); WRITE_STRING_FIELD(var_name); + WRITE_INT32_FIELD(path_length); } /* serialization function for the cypher_target_node ExtensibleNode. */ @@ -427,6 +428,7 @@ void out_cypher_update_information(StringInfo str, const ExtensibleNode *node) WRITE_STRING_FIELD(graph_name); WRITE_INT32_FIELD(graph_oid); WRITE_STRING_FIELD(clause_name); + WRITE_INT32_FIELD(set_item_count); } /* serialization function for the cypher_update_item ExtensibleNode. */ @@ -455,6 +457,7 @@ void out_cypher_delete_information(StringInfo str, const ExtensibleNode *node) WRITE_STRING_FIELD(graph_name); WRITE_INT32_FIELD(graph_oid); WRITE_BOOL_FIELD(detach); + WRITE_INT32_FIELD(delete_item_count); } /* serialization function for the cypher_delete_item ExtensibleNode. */ @@ -475,8 +478,11 @@ void out_cypher_merge_information(StringInfo str, const ExtensibleNode *node) WRITE_INT32_FIELD(graph_oid); WRITE_INT32_FIELD(merge_function_attr); WRITE_NODE_FIELD(path); + WRITE_INT32_FIELD(path_length); WRITE_NODE_FIELD(on_match_set_info); WRITE_NODE_FIELD(on_create_set_info); + WRITE_INT32_FIELD(on_match_set_item_count); + WRITE_INT32_FIELD(on_create_set_item_count); } /* diff --git a/src/backend/nodes/cypher_readfuncs.c b/src/backend/nodes/cypher_readfuncs.c index 00d7d2239..4187683ba 100644 --- a/src/backend/nodes/cypher_readfuncs.c +++ b/src/backend/nodes/cypher_readfuncs.c @@ -213,6 +213,7 @@ void read_cypher_create_path(struct ExtensibleNode *node) READ_NODE_FIELD(target_nodes); READ_INT_FIELD(path_attr_num); READ_STRING_FIELD(var_name); + READ_INT_FIELD(path_length); } /* @@ -253,6 +254,7 @@ void read_cypher_update_information(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_UINT_FIELD(graph_oid); READ_STRING_FIELD(clause_name); + READ_INT_FIELD(set_item_count); local_node->last_update_indexes = NULL; local_node->last_update_index_count = 0; @@ -291,9 +293,9 @@ void read_cypher_delete_information(struct ExtensibleNode *node) READ_STRING_FIELD(graph_name); READ_UINT_FIELD(graph_oid); READ_BOOL_FIELD(detach); + READ_INT_FIELD(delete_item_count); local_node->delete_item_positions = NULL; - local_node->delete_item_count = 0; local_node->delete_item_positions_valid = false; } @@ -321,8 +323,11 @@ void read_cypher_merge_information(struct ExtensibleNode *node) READ_UINT_FIELD(graph_oid); READ_INT_FIELD(merge_function_attr); READ_NODE_FIELD(path); + READ_INT_FIELD(path_length); READ_NODE_FIELD(on_match_set_info); READ_NODE_FIELD(on_create_set_info); + READ_INT_FIELD(on_match_set_item_count); + READ_INT_FIELD(on_create_set_item_count); } /* diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 9b6016cd6..441572af7 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -1499,6 +1499,7 @@ static Query *transform_cypher_delete(cypher_parsestate *cpstate, delete_data->delete_items = transform_cypher_delete_item_list(cpstate, self->exprs, query); + delete_data->delete_item_count = list_length(delete_data->delete_items); delete_data->graph_name = cpstate->graph_name; delete_data->graph_oid = cpstate->graph_oid; delete_data->detach = self->detach; @@ -2171,6 +2172,7 @@ cypher_update_information *transform_cypher_remove_item_list( info->set_items = NIL; info->flags = 0; + info->set_item_count = 0; foreach (li, remove_item_list) { @@ -2267,6 +2269,7 @@ cypher_update_information *transform_cypher_remove_item_list( item->prop_name = property_name; info->set_items = lappend(info->set_items, item); + info->set_item_count++; } return info; @@ -2282,6 +2285,7 @@ cypher_update_information *transform_cypher_set_item_list( info->set_items = NIL; info->flags = 0; + info->set_item_count = 0; foreach (li, set_item_list) { @@ -2462,6 +2466,7 @@ cypher_update_information *transform_cypher_set_item_list( query->targetList = lappend(query->targetList, target_item); info->set_items = lappend(info->set_items, item); + info->set_item_count++; } return info; @@ -6323,6 +6328,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, bool in_path = path->var_name != NULL; ccp->path_attr_num = InvalidAttrNumber; + ccp->path_length = 0; if (in_path) { @@ -6362,6 +6368,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, NULL); @@ -6390,6 +6397,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; entity = make_transform_entity(cpstate, ENT_EDGE, (Node *)edge, NULL); @@ -6414,7 +6422,7 @@ transform_cypher_create_path(cypher_parsestate *cpstate, List **target_list, { TargetEntry *te; - if (list_length(transformed_path) < 1) + if (ccp->path_length < 1) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), @@ -6521,8 +6529,9 @@ transform_create_cypher_edge(cypher_parsestate *cpstate, List **target_list, parent = list_make1(rv); - created_label_relid = create_label(cpstate->graph_name, edge->label, - LABEL_TYPE_EDGE, parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ @@ -6812,8 +6821,9 @@ transform_create_cypher_new_node(cypher_parsestate *cpstate, parent = list_make1(rv); - created_label_relid = create_label(cpstate->graph_name, node->label, - LABEL_TYPE_VERTEX, parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags = CYPHER_TARGET_NODE_FLAG_INSERT; @@ -7406,6 +7416,7 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, merge_information->graph_oid = cpstate->graph_oid; merge_information->path = merge_path; + merge_information->path_length = merge_path->path_length; /* Transform ON MATCH SET items, if any */ if (self->on_match != NIL) @@ -7419,6 +7430,8 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, resolve_merge_set_exprs( merge_information->on_match_set_info->set_items, query->targetList, "ON MATCH SET"); + merge_information->on_match_set_item_count = + merge_information->on_match_set_info->set_item_count; } /* Transform ON CREATE SET items, if any */ @@ -7433,6 +7446,8 @@ static Query *transform_cypher_merge(cypher_parsestate *cpstate, resolve_merge_set_exprs( merge_information->on_create_set_info->set_items, query->targetList, "ON CREATE SET"); + merge_information->on_create_set_item_count = + merge_information->on_create_set_info->set_item_count; } if (!clause->next) @@ -7917,6 +7932,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, bool in_path = path->var_name != NULL; ccp->path_attr_num = InvalidAttrNumber; + ccp->path_length = 0; foreach (lc, path->path) { @@ -7956,6 +7972,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; } else if (is_ag_node(lfirst(lc), cypher_relationship)) { @@ -7997,6 +8014,7 @@ transform_cypher_merge_path(cypher_parsestate *cpstate, List **target_list, } transformed_path = lappend(transformed_path, rel); + ccp->path_length++; } else { @@ -8097,8 +8115,9 @@ transform_merge_cypher_edge(cypher_parsestate *cpstate, List **target_list, parent = list_make1(rv); /* create the label */ - created_label_relid = create_label(cpstate->graph_name, edge->label, - LABEL_TYPE_EDGE, parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, edge->label, + LABEL_TYPE_EDGE, parent); } /* lock the relation of the label */ @@ -8235,8 +8254,9 @@ transform_merge_cypher_node(cypher_parsestate *cpstate, List **target_list, parent = list_make1(rv); /* create the label */ - created_label_relid = create_label(cpstate->graph_name, node->label, - LABEL_TYPE_VERTEX, parent); + created_label_relid = create_label_with_graph_oid( + cpstate->graph_name, cpstate->graph_oid, node->label, + LABEL_TYPE_VERTEX, parent); } rel->flags |= CYPHER_TARGET_NODE_FLAG_INSERT; diff --git a/src/backend/parser/cypher_parse_node.c b/src/backend/parser/cypher_parse_node.c index 1adc489a5..3acbd6214 100644 --- a/src/backend/parser/cypher_parse_node.c +++ b/src/backend/parser/cypher_parse_node.c @@ -163,6 +163,14 @@ label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, label_cache_data *label_cache; label_cache_entry *entry; + if (root_cpstate->last_label_cache_data != NULL && + root_cpstate->last_label_cache_graph_oid == cpstate->graph_oid && + root_cpstate->last_label_cache_generation == generation && + strcmp(root_cpstate->last_label_cache_name, label_name) == 0) + { + return root_cpstate->last_label_cache_data; + } + foreach (lc, root_cpstate->label_cache_entries) { entry = lfirst(lc); @@ -171,6 +179,10 @@ label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, entry->generation == generation && strcmp(entry->label_name, label_name) == 0) { + root_cpstate->last_label_cache_name = entry->label_name; + root_cpstate->last_label_cache_graph_oid = entry->graph_oid; + root_cpstate->last_label_cache_generation = entry->generation; + root_cpstate->last_label_cache_data = entry->label_cache; return entry->label_cache; } } @@ -189,6 +201,10 @@ label_cache_data *get_label_cache_data(cypher_parsestate *cpstate, entry->label_cache = label_cache; root_cpstate->label_cache_entries = lappend(root_cpstate->label_cache_entries, entry); + root_cpstate->last_label_cache_name = entry->label_name; + root_cpstate->last_label_cache_graph_oid = entry->graph_oid; + root_cpstate->last_label_cache_generation = entry->generation; + root_cpstate->last_label_cache_data = entry->label_cache; return label_cache; } diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index 30849e54a..d4c7cc91d 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -201,6 +201,10 @@ static Datum process_access_operator_result_no_copy(FunctionCallInfo fcinfo, agtype_value *agtv, bool as_text, bool needs_free); +static agtype *build_vertex_agtype(graphid id, const char *label, + agtype *properties); +static agtype *build_edge_agtype(graphid id, graphid start_id, graphid end_id, + const char *label, agtype *properties); /* typecast functions */ static void agtype_typecast_object(agtype_in_state *state, char *annotation); static void agtype_typecast_array(agtype_in_state *state, char *annotation); @@ -2623,18 +2627,16 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agt_result); } -Datum make_path(List *path) +Datum make_path_with_length(List *path, int path_len) { agtype *agt_result; ListCell *lc; agtype_in_state result; - int path_len; int i = 1; memset(&result, 0, sizeof(agtype_in_state)); result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, NULL); - path_len = list_length(path); if (path_len < 1) { @@ -2697,8 +2699,58 @@ Datum make_path(List *path) PG_RETURN_POINTER(agt_result); } +Datum make_path(List *path) +{ + return make_path_with_length(path, list_length(path)); +} + PG_FUNCTION_INFO_V1(_agtype_build_vertex); +static agtype *build_vertex_agtype(graphid id, const char *label, + agtype *properties) +{ + agtype_build_state *bstate; + agtype *rawscalar; + agtype *vertex; + bool properties_was_null = false; + + if (properties == NULL) + { + properties_was_null = true; + bstate = init_agtype_build_state(0, AGT_FOBJECT); + properties = build_agtype(bstate); + pfree_agtype_build_state(bstate); + } + else if (!AGT_ROOT_IS_OBJECT(properties)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_vertex() properties argument must be an object"))); + } + + bstate = init_agtype_build_state(3, AGT_FOBJECT); + write_string(bstate, "id"); + write_string(bstate, "label"); + write_string(bstate, "properties"); + write_graphid(bstate, id); + write_string(bstate, (char *)label); + write_container(bstate, properties); + vertex = build_agtype(bstate); + pfree_agtype_build_state(bstate); + + bstate = init_agtype_build_state(1, AGT_FARRAY | AGT_FSCALAR); + write_extended(bstate, vertex, AGT_HEADER_VERTEX); + rawscalar = build_agtype(bstate); + pfree_agtype_build_state(bstate); + + if (properties_was_null) + { + pfree(properties); + } + + return rawscalar; +} + /* * SQL function agtype_build_vertex(graphid, cstring, agtype) */ @@ -2707,9 +2759,7 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) graphid id; char *label; agtype *properties; - agtype_build_state *bstate; - agtype *rawscalar; - agtype *vertex; + agtype *result; /* handles null */ if (fcinfo->args[0].isnull) @@ -2730,60 +2780,91 @@ Datum _agtype_build_vertex(PG_FUNCTION_ARGS) if (fcinfo->args[2].isnull) { - bstate = init_agtype_build_state(0, AGT_FOBJECT); - properties = build_agtype(bstate); - pfree_agtype_build_state(bstate); + properties = NULL; } else { properties = AG_GET_ARG_AGTYPE_P(2); + } - if (!AGT_ROOT_IS_OBJECT(properties)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_vertex() properties argument must be an object"))); - } + result = build_vertex_agtype(id, label, properties); + + PG_FREE_IF_COPY(label, 1); + if (properties != NULL) + { + PG_FREE_IF_COPY(properties, 2); } - bstate = init_agtype_build_state(3, AGT_FOBJECT); + PG_RETURN_POINTER(result); +} + +Datum make_vertex(Datum id, Datum label, Datum properties) +{ + return PointerGetDatum(build_vertex_agtype(DATUM_GET_GRAPHID(id), + DatumGetCString(label), + DATUM_GET_AGTYPE_P(properties))); +} + +PG_FUNCTION_INFO_V1(_agtype_build_edge); + +static agtype *build_edge_agtype(graphid id, graphid start_id, graphid end_id, + const char *label, agtype *properties) +{ + agtype_build_state *bstate; + agtype *edge; + agtype *rawscalar; + bool properties_was_null = false; + + if (properties == NULL) + { + properties_was_null = true; + bstate = init_agtype_build_state(0, AGT_FOBJECT); + properties = build_agtype(bstate); + pfree_agtype_build_state(bstate); + } + else if (!AGT_ROOT_IS_OBJECT(properties)) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_agtype_build_edge() properties argument must be an object"))); + } + + bstate = init_agtype_build_state(5, AGT_FOBJECT); write_string(bstate, "id"); write_string(bstate, "label"); + write_string(bstate, "end_id"); + write_string(bstate, "start_id"); write_string(bstate, "properties"); write_graphid(bstate, id); - write_string(bstate, label); + write_string(bstate, (char *)label); + write_graphid(bstate, end_id); + write_graphid(bstate, start_id); write_container(bstate, properties); - vertex = build_agtype(bstate); + edge = build_agtype(bstate); pfree_agtype_build_state(bstate); bstate = init_agtype_build_state(1, AGT_FARRAY | AGT_FSCALAR); - write_extended(bstate, vertex, AGT_HEADER_VERTEX); + write_extended(bstate, edge, AGT_HEADER_EDGE); rawscalar = build_agtype(bstate); pfree_agtype_build_state(bstate); - PG_FREE_IF_COPY(label, 1); - PG_FREE_IF_COPY(properties, 2); - - PG_RETURN_POINTER(rawscalar); -} + if (properties_was_null) + { + pfree(properties); + } -Datum make_vertex(Datum id, Datum label, Datum properties) -{ - return DirectFunctionCall3(_agtype_build_vertex, id, label, properties); + return rawscalar; } -PG_FUNCTION_INFO_V1(_agtype_build_edge); - /* * SQL function agtype_build_edge(graphid, graphid, graphid, cstring, agtype) */ Datum _agtype_build_edge(PG_FUNCTION_ARGS) { - agtype_build_state *bstate; - agtype *edge, *rawscalar; graphid id, start_id, end_id; char *label; agtype *properties; + agtype *result; /* process graph id */ if (fcinfo->args[0].isnull) @@ -2829,52 +2910,32 @@ Datum _agtype_build_edge(PG_FUNCTION_ARGS) /* if the properties object is null, push an empty object */ if (fcinfo->args[4].isnull) { - bstate = init_agtype_build_state(0, AGT_FOBJECT); - properties = build_agtype(bstate); - pfree_agtype_build_state(bstate); + properties = NULL; } else { properties = AG_GET_ARG_AGTYPE_P(4); - - if (!AGT_ROOT_IS_OBJECT(properties)) - { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("_agtype_build_edge() properties argument must be an object"))); - } } - bstate = init_agtype_build_state(5, AGT_FOBJECT); - write_string(bstate, "id"); - write_string(bstate, "label"); - write_string(bstate, "end_id"); - write_string(bstate, "start_id"); - write_string(bstate, "properties"); - write_graphid(bstate, id); - write_string(bstate, label); - write_graphid(bstate, end_id); - write_graphid(bstate, start_id); - write_container(bstate, properties); - edge = build_agtype(bstate); - pfree_agtype_build_state(bstate); - - bstate = init_agtype_build_state(1, AGT_FARRAY | AGT_FSCALAR); - write_extended(bstate, edge, AGT_HEADER_EDGE); - rawscalar = build_agtype(bstate); - pfree_agtype_build_state(bstate); + result = build_edge_agtype(id, start_id, end_id, label, properties); PG_FREE_IF_COPY(label, 3); - PG_FREE_IF_COPY(properties, 4); + if (properties != NULL) + { + PG_FREE_IF_COPY(properties, 4); + } - PG_RETURN_POINTER(rawscalar); + PG_RETURN_POINTER(result); } Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, Datum properties) { - return DirectFunctionCall5(_agtype_build_edge, id, startid, endid, label, - properties); + return PointerGetDatum(build_edge_agtype(DATUM_GET_GRAPHID(id), + DATUM_GET_GRAPHID(startid), + DATUM_GET_GRAPHID(endid), + DatumGetCString(label), + DATUM_GET_AGTYPE_P(properties))); } static agtype_value *agtype_build_map_as_agtype_value(FunctionCallInfo fcinfo) @@ -5946,10 +6007,10 @@ Datum agtype_typecast_vertex(PG_FUNCTION_ARGS) errmsg("vertex typecast object has invalid or missing properties"))); /* Hand it off to the build vertex routine */ - result = DirectFunctionCall3(_agtype_build_vertex, - Int64GetDatum(agtv_graphid->val.int_value), - CStringGetDatum(agtv_label->val.string.val), - PointerGetDatum(agtype_value_to_agtype(agtv_properties))); + result = PointerGetDatum(build_vertex_agtype( + agtv_graphid->val.int_value, + agtv_label->val.string.val, + agtype_value_to_agtype(agtv_properties))); return result; } @@ -6039,12 +6100,12 @@ Datum agtype_typecast_edge(PG_FUNCTION_ARGS) errmsg("edge typecast object has an invalid or missing end_id"))); /* Hand it off to the build edge routine */ - result = DirectFunctionCall5(_agtype_build_edge, - Int64GetDatum(agtv_graphid->val.int_value), - Int64GetDatum(agtv_startid->val.int_value), - Int64GetDatum(agtv_endid->val.int_value), - CStringGetDatum(agtv_label->val.string.val), - PointerGetDatum(agtype_value_to_agtype(agtv_properties))); + result = PointerGetDatum(build_edge_agtype( + agtv_graphid->val.int_value, + agtv_startid->val.int_value, + agtv_endid->val.int_value, + agtv_label->val.string.val, + agtype_value_to_agtype(agtv_properties))); return result; } @@ -6414,6 +6475,7 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, AclResult aclresult; TupleTableSlot *slot = NULL; Oid index_oid; + agtype *properties_agt = NULL; bool should_free_tuple = false; bool isnull; @@ -6534,8 +6596,14 @@ static Datum get_vertex(const char *vertex_label, Oid vertex_label_table_oid, } /* reconstruct the vertex */ - result = DirectFunctionCall3(_agtype_build_vertex, id, - CStringGetDatum(vertex_label), properties); + properties_agt = DATUM_GET_AGTYPE_P(properties); + result = PointerGetDatum(build_vertex_agtype(DATUM_GET_GRAPHID(id), + vertex_label, + properties_agt)); + if ((Pointer)properties_agt != DatumGetPointer(properties)) + { + pfree(properties_agt); + } /* end the scan and close the relation with new cleanup logic */ if (scan_desc) diff --git a/src/backend/utils/cache/ag_cache.c b/src/backend/utils/cache/ag_cache.c index e669d3908..b205a0884 100644 --- a/src/backend/utils/cache/ag_cache.c +++ b/src/backend/utils/cache/ag_cache.c @@ -1389,6 +1389,30 @@ label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph) return search_label_seq_name_graph_cache_miss(&name_key, graph); } +const char *get_label_cache_relation_name(label_cache_data *label_cache) +{ + char *relname; + + Assert(label_cache != NULL); + + if (label_cache->relation_name.data[0] != '\0') + return NameStr(label_cache->relation_name); + + relname = get_rel_name(label_cache->relation); + if (relname == NULL) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("label relation %u does not exist", + label_cache->relation))); + } + + namestrcpy(&label_cache->relation_name, relname); + pfree(relname); + + return NameStr(label_cache->relation_name); +} + Oid get_label_seq_relation_cached(const label_cache_data *label_cache, Oid namespace) { @@ -1579,12 +1603,7 @@ static void fill_label_cache_data(label_cache_data *cache_data, value = heap_getattr(tuple, Anum_ag_label_relation, tuple_desc, &is_null); Assert(!is_null); cache_data->relation = DatumGetObjectId(value); - { - char *relname = get_rel_name(cache_data->relation); - - namestrcpy(&cache_data->relation_name, relname); - pfree(relname); - } + cache_data->relation_name.data[0] = '\0'; /* ag_label.seq_name */ value = heap_getattr(tuple, Anum_ag_label_seq_name, tuple_desc, &is_null); Assert(!is_null); diff --git a/src/backend/utils/graph_generation.c b/src/backend/utils/graph_generation.c index 29056518b..bebe62902 100644 --- a/src/backend/utils/graph_generation.c +++ b/src/backend/utils/graph_generation.c @@ -34,6 +34,9 @@ static void queue_vertex_insert(batch_insert_state *batch_state, static void queue_edge_insert(batch_insert_state *batch_state, graphid edge_id, graphid start_id, graphid end_id, agtype *edge_properties); +static void create_complete_graph_internal(Name graph_name, int64 no_vertices, + Name edge_label_name, + Name vtx_label_name); /* * Auxiliary function to get the next internal value in the graph, * so a new object (node or edge) graph id can be composed. @@ -116,16 +119,14 @@ PG_FUNCTION_INFO_V1(create_complete_graph); * SELECT * FROM ag_catalog.create_complete_graph('graph_name',no_of_nodes, 'edge_label', 'node_label'=NULL); */ -Datum create_complete_graph(PG_FUNCTION_ARGS) +static void create_complete_graph_internal(Name graph_name, int64 no_vertices, + Name edge_label_name, + Name vtx_label_name) { Oid graph_oid; - Name graph_name; - int64 no_vertices; int64 i,j,vid = 1, eid, start_vid, end_vid; - Name vtx_label_name = NULL; - Name edge_label_name; int32 vtx_label_id; int32 edge_label_id; @@ -152,35 +153,12 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) Oid nsp_id; int64 lid; - if (PG_ARGISNULL(0)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("graph name can not be NULL"))); - } - - if (PG_ARGISNULL(1)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("number of nodes can not be NULL"))); - } - - if (PG_ARGISNULL(2)) - { - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("edge label can not be NULL"))); - } - - graph_name = PG_GETARG_NAME(0); - no_vertices = (int64) PG_GETARG_INT64(1); - edge_label_name = PG_GETARG_NAME(2); - graph_name_str = NameStr(*graph_name); vtx_name_str = AG_DEFAULT_LABEL_VERTEX; edge_name_str = NameStr(*edge_label_name); - if (!PG_ARGISNULL(3)) + if (vtx_label_name != NULL) { - vtx_label_name = PG_GETARG_NAME(3); vtx_name_str = NameStr(*vtx_label_name); /* Check if vertex and edge label are same */ @@ -194,8 +172,9 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) graph_cache = search_graph_name_cache_cached(graph_name_str); if (graph_cache == NULL) { - DirectFunctionCall1(create_graph, CStringGetDatum(graph_name->data)); - graph_cache = search_graph_name_cache_cached(graph_name_str); + Oid created_graph_oid = create_graph_internal(graph_name); + + graph_cache = search_graph_namespace_cache_cached(created_graph_oid); if (graph_cache == NULL) { ereport(ERROR, @@ -203,6 +182,8 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) errmsg("graph \"%s\" was not found after creation", graph_name_str))); } + ereport(NOTICE, + (errmsg("graph \"%s\" has been created", graph_name_str))); } graph_oid = graph_cache->oid; @@ -212,8 +193,9 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) { List *parent = list_make1(makeRangeVar( graph_name_str, pstrdup(AG_DEFAULT_LABEL_VERTEX), -1)); - Oid label_relid = create_label(graph_name_str, vtx_name_str, - LABEL_TYPE_VERTEX, parent); + Oid label_relid = create_label_with_graph_oid( + graph_name_str, graph_oid, vtx_name_str, LABEL_TYPE_VERTEX, + parent); vertex_cache = search_label_relation_cache_cached(label_relid); if (vertex_cache == NULL) @@ -232,8 +214,9 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) { List *parent = list_make1(makeRangeVar( graph_name_str, pstrdup(AG_DEFAULT_LABEL_EDGE), -1)); - Oid label_relid = create_label(graph_name_str, edge_name_str, - LABEL_TYPE_EDGE, parent); + Oid label_relid = create_label_with_graph_oid( + graph_name_str, graph_oid, edge_name_str, LABEL_TYPE_EDGE, + parent); edge_cache = search_label_relation_cache_cached(label_relid); if (edge_cache == NULL) @@ -294,6 +277,44 @@ Datum create_complete_graph(PG_FUNCTION_ARGS) } finish_batch_insert(&edge_batch_state); table_close(edge_rel, RowExclusiveLock); +} + +Datum create_complete_graph(PG_FUNCTION_ARGS) +{ + Name graph_name; + Name edge_label_name; + Name vtx_label_name = NULL; + int64 no_vertices; + + if (PG_ARGISNULL(0)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("graph name can not be NULL"))); + } + + if (PG_ARGISNULL(1)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("number of nodes can not be NULL"))); + } + + if (PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("edge label can not be NULL"))); + } + + graph_name = PG_GETARG_NAME(0); + no_vertices = (int64) PG_GETARG_INT64(1); + edge_label_name = PG_GETARG_NAME(2); + + if (!PG_ARGISNULL(3)) + { + vtx_label_name = PG_GETARG_NAME(3); + } + + create_complete_graph_internal(graph_name, no_vertices, edge_label_name, + vtx_label_name); PG_RETURN_VOID(); } @@ -325,11 +346,11 @@ PG_FUNCTION_INFO_V1(age_create_barbell_graph); Datum age_create_barbell_graph(PG_FUNCTION_ARGS) { - FunctionCallInfo arguments; Oid graph_oid; Name graph_name; char* graph_name_str; + int64 graph_size; int64 start_node_index, end_node_index, nextval; Name node_label_name = NULL; @@ -350,8 +371,6 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) agtype* properties = NULL; - arguments = fcinfo; - /* Checking for possible NULL arguments */ /* Name graph_name */ if (PG_ARGISNULL(0)) @@ -364,11 +383,12 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) graph_name_str = NameStr(*graph_name); /* int graph size (number of nodes in each complete graph) */ - if (PG_ARGISNULL(1) && PG_GETARG_INT32(1) < 3) + if (PG_ARGISNULL(1) || PG_GETARG_INT32(1) < 3) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Graph size cannot be NULL or lower than 3"))); } + graph_size = (int64) PG_GETARG_INT32(1); /* * int64 bridge_size: currently only stays at zero. @@ -383,13 +403,13 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) /* node label: if null, gets default label, which is "_ag_label_vertex" */ if (PG_ARGISNULL(3)) { - namestrcpy(node_label_name, AG_DEFAULT_LABEL_VERTEX); + node_label_str = AG_DEFAULT_LABEL_VERTEX; } else { node_label_name = PG_GETARG_NAME(3); + node_label_str = NameStr(*node_label_name); } - node_label_str = NameStr(*node_label_name); /* Name edge_label */ if (PG_ARGISNULL(5)) @@ -403,14 +423,10 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) /* create two separate complete graphs */ - DirectFunctionCall4(create_complete_graph, arguments->args[0].value, - arguments->args[1].value, - arguments->args[5].value, - arguments->args[3].value); - DirectFunctionCall4(create_complete_graph, arguments->args[0].value, - arguments->args[1].value, - arguments->args[5].value, - arguments->args[3].value); + create_complete_graph_internal(graph_name, graph_size, edge_label_name, + node_label_name); + create_complete_graph_internal(graph_name, graph_size, edge_label_name, + node_label_name); /* * Fetching caches to get next values for graph id's, and access nodes @@ -451,7 +467,7 @@ Datum age_create_barbell_graph(PG_FUNCTION_ARGS) /* first created node, from the first complete graph */ start_node_index = 1; /* last created node, second graph */ - end_node_index = arguments->args[1].value*2; + end_node_index = graph_size * 2; /* next index to be assigned to a node or edge */ nextval = get_nextval_internal(graph_cache, edge_cache); diff --git a/src/backend/utils/load/age_load.c b/src/backend/utils/load/age_load.c index a6de27359..a407787aa 100644 --- a/src/backend/utils/load/age_load.c +++ b/src/backend/utils/load/age_load.c @@ -743,8 +743,8 @@ static Oid get_or_create_graph(const Name graph_name) return graph_cache->oid; } - create_graph_internal(graph_name); - graph_cache = search_graph_name_cache_cached(graph_name_str); + graph_cache = search_graph_namespace_cache_cached( + create_graph_internal(graph_name)); if (graph_cache == NULL) { ereport(ERROR, @@ -798,7 +798,9 @@ static label_cache_data *get_or_create_label(Oid graph_oid, char *graph_name, rv = makeRangeVar(graph_name, pstrdup(default_label), -1); parent = list_make1(rv); - label_relid = create_label(graph_name, label_name, label_kind, parent); + label_relid = create_label_with_graph_oid(graph_name, graph_oid, + label_name, label_kind, + parent); label_cache = search_label_relation_cache_cached(label_relid); if (label_cache == NULL) { diff --git a/src/include/commands/label_commands.h b/src/include/commands/label_commands.h index 9baf84526..dd8ce6132 100644 --- a/src/include/commands/label_commands.h +++ b/src/include/commands/label_commands.h @@ -54,6 +54,9 @@ Oid create_label(char *graph_name, char *label_name, char label_type, List *parents); +Oid create_label_with_graph_oid(char *graph_name, Oid graph_oid, + char *label_name, char label_type, + List *parents); Datum create_vlabel(PG_FUNCTION_ARGS); diff --git a/src/include/nodes/cypher_nodes.h b/src/include/nodes/cypher_nodes.h index 80d6c81e1..323d6b8d2 100644 --- a/src/include/nodes/cypher_nodes.h +++ b/src/include/nodes/cypher_nodes.h @@ -310,6 +310,7 @@ typedef struct cypher_create_path List *target_nodes; AttrNumber path_attr_num; char *var_name; + int path_length; } cypher_create_path; /* @@ -463,6 +464,7 @@ typedef struct cypher_update_information char *graph_name; uint32 graph_oid; char *clause_name; + int set_item_count; int *last_update_indexes; int last_update_index_count; bool last_update_indexes_valid; @@ -510,8 +512,11 @@ typedef struct cypher_merge_information uint32 graph_oid; AttrNumber merge_function_attr; cypher_create_path *path; + int path_length; cypher_update_information *on_match_set_info; /* NULL if no ON MATCH SET */ cypher_update_information *on_create_set_info; /* NULL if no ON CREATE SET */ + int on_match_set_item_count; + int on_create_set_item_count; } cypher_merge_information; /* grammar node for typecasts */ diff --git a/src/include/parser/cypher_parse_node.h b/src/include/parser/cypher_parse_node.h index f4f4fd8ba..561aa62dc 100644 --- a/src/include/parser/cypher_parse_node.h +++ b/src/include/parser/cypher_parse_node.h @@ -41,6 +41,10 @@ typedef struct cypher_parsestate int default_alias_num; List *entities; List *label_cache_entries; + const char *last_label_cache_name; + Oid last_label_cache_graph_oid; + uint64 last_label_cache_generation; + label_cache_data *last_label_cache_data; List *property_constraint_quals; bool subquery_where_flag; /* flag for knowing we are in a subquery where */ /* diff --git a/src/include/utils/ag_cache.h b/src/include/utils/ag_cache.h index b61e602ea..347b33505 100644 --- a/src/include/utils/ag_cache.h +++ b/src/include/utils/ag_cache.h @@ -58,6 +58,7 @@ label_cache_data *search_label_graph_oid_cache_cached(Oid graph, int32 id); label_cache_data *search_label_relation_cache(Oid relation); label_cache_data *search_label_relation_cache_cached(Oid relation); label_cache_data *search_label_seq_name_graph_cache(const char *name, Oid graph); +const char *get_label_cache_relation_name(label_cache_data *label_cache); Oid get_label_seq_relation_cached(const label_cache_data *label_cache, Oid namespace); diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 66cf37b17..94d1fff87 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -644,6 +644,7 @@ Datum make_vertex(Datum id, Datum label, Datum properties); Datum make_edge(Datum id, Datum startid, Datum endid, Datum label, Datum properties); Datum make_path(List *path); +Datum make_path_with_length(List *path, int path_len); agtype_value *agtype_value_build_vertex(graphid id, char *label, Datum properties); agtype_value *agtype_value_build_edge(graphid id, char *label, graphid end_id, From 0395b6b2aa43bb2c5c9ed821c6b5c3f846a26785 Mon Sep 17 00:00:00 2001 From: emotionbug Date: Sat, 30 May 2026 06:44:39 +0900 Subject: [PATCH 15/15] Consolidate VLE traversal fast paths Collapse the recent VLE micro-optimization commits into one coherent change so the history describes the traversal work as a single reviewable unit instead of a long sequence of mechanical fast paths. The combined change keeps the VLE path materialization, slice, boundary, endpoint, and uniqueness optimizations together because they all reduce repeated work in the same expansion pipeline. This preserves the final code state while making the branch easier to review and bisect. The commit covers direct stack scans, cached frontier state, empty-range short circuits, direct relationship and node projection paths, reverse index rewrites, endpoint mode validation, and regression coverage for empty VLE ranges. --- regress/expected/cypher_vle.out | 613 ++ regress/sql/cypher_vle.sql | 120 + sql/agtype_typecast.sql | 355 + src/backend/parser/cypher_clause.c | 252 +- src/backend/parser/cypher_expr.c | 8545 +++++++++++++++++++--- src/backend/utils/adt/age_global_graph.c | 110 +- src/backend/utils/adt/age_vle.c | 5387 +++++++++++++- src/backend/utils/adt/agtype.c | 334 +- src/backend/utils/adt/agtype_util.c | 113 + src/include/utils/age_global_graph.h | 6 + src/include/utils/age_vle.h | 24 + src/include/utils/agtype.h | 5 + 12 files changed, 14193 insertions(+), 1671 deletions(-) diff --git a/regress/expected/cypher_vle.out b/regress/expected/cypher_vle.out index 6574e0608..b9d031603 100644 --- a/regress/expected/cypher_vle.out +++ b/regress/expected/cypher_vle.out @@ -280,6 +280,77 @@ SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->(v) RETURN count(*) $$) AS (e 2922 (1 row) +\pset format unaligned +-- Empty direct age_vle finite range should not traverse +SELECT count(edges) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '2'::agtype, '1'::agtype); +count +0 +(1 row) +-- Direct runtime nodes()/relationships() should project compact VLE paths +SELECT count(age_nodes(edges)), count(age_relationships(edges)), sum((age_length(edges))::text::int) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '3'::agtype, '1'::agtype); +count|count|sum +2|2|6 +(1 row) +-- Optimized anonymous terminal: bound start, unused anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(*) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH p=(u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(p) $$) AS (e agtype); +e +2 +(1 row) +-- Optimized right-bound paths-to: anonymous start, selective anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (e agtype); +e +2 +(1 row) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_36 + -> Function Scan on age_vle _age_default_alias_0 +(4 rows) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_39 + -> Function Scan on age_vle _age_default_alias_0 +(4 rows) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_42 + -> Nested Loop + -> Seq Scan on begin _age_default_vle_function_start_var_41 + -> Function Scan on age_vle _age_default_alias_0 +(6 rows) +-- Optimized two-bound lower-zero VLE: age_vle handles start=end zero-path semantics. +SELECT * FROM cypher('cypher_vle', $$MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +e +0 +(1 row) +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*0..2 {name: "main edge"}]->(u) RETURN count(*) $$) AS (e agtype); +e +1 +(1 row) +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +QUERY PLAN +Aggregate + -> Nested Loop + -> Seq Scan on "end" _age_default_vle_function_end_var_49 + -> Nested Loop + -> Seq Scan on begin _age_default_vle_function_start_var_48 + -> Function Scan on age_vle _age_default_alias_0 +(6 rows) +\pset format aligned -- Should find 2 SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)<-[e*]-(v:end) RETURN e $$) AS (e agtype); e @@ -929,6 +1000,522 @@ SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN properties(e[2]) $ (3 rows) +\pset format unaligned +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN e[1].arry[2].stats, tail(e)[0].id, relationships(p)[1].id $$) as (edge_index_stats agtype, tail_index_id agtype, relationship_index_id agtype); +edge_index_stats|tail_index_id|relationship_index_id +|| +|1|1 +{"age": 1000}|3|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(e[0]), label(e[0]) $$) as (edge_type agtype, edge_label agtype); +edge_type|edge_label +"knows"|"knows" +"knows"|"knows" +"knows"|"knows" +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0] = e[0], e[0] = e[1] $$) as (same_edge agtype, different_edge agtype); +same_edge|different_edge +true|false +true|false +true|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN head(e) IN e, tail(e)[0] IN e, relationships(p)[1] IN relationships(p), last(e) IN e $$) as (head_in_edges agtype, tail_index_in_edges agtype, relationship_index_in_path agtype, last_in_edges agtype); +head_in_edges|tail_index_in_edges|relationship_index_in_path|last_in_edges +true|true|true|true +true|true|true|true +true|true|true|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last([e[0]]) = head(e), head([tail(e)[0]]) = last(e), last([e[1]]) IN e $$) as (singleton_head_equal agtype, singleton_tail_equal agtype, singleton_in_edges agtype); +singleton_head_equal|singleton_tail_equal|singleton_in_edges +true|true|true +true|true|true +true|true|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] IN e, reverse(tail(e))[0] IN e, tail(tail(e))[0] IN e $$) as (tail_reverse_in_edges agtype, reverse_tail_in_edges agtype, double_tail_in_edges agtype); +tail_reverse_in_edges|reverse_tail_in_edges|double_tail_in_edges +true|true| +true|true| +true|true| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN tail(reverse(e))[0] = e[0], reverse(tail(e))[0] = e[1], tail(tail(e))[0] = e[0], reverse(tail(e))[0] = relationships(p)[1] $$) as (nested_tail_reverse_same agtype, nested_reverse_tail_same agtype, nested_double_tail_same agtype, nested_path_relationship_same agtype); +nested_tail_reverse_same|nested_reverse_tail_same|nested_double_tail_same|nested_path_relationship_same +true|true||true +true|true||true +true|true||true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] = reverse(tail(e))[0], tail(reverse(e))[0] = tail(reverse(e))[0], reverse(tail(e))[0] = tail(reverse(e))[0], tail(tail(e))[0] = tail(tail(e))[0] $$) as (nested_cross_same agtype, nested_tail_reverse_self agtype, nested_cross_symmetric agtype, nested_empty_self agtype); +nested_cross_same|nested_tail_reverse_self|nested_cross_symmetric|nested_empty_self +false|true|false| +false|true|false| +false|true|false| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1].id $$) as (relationship_id agtype); +relationship_id + +1 +3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)) $$) as (relationship_count agtype); +relationship_count +2 +2 +2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)[1..]), size(nodes(p)[1..]), size(tail(nodes(p))[0..1]) $$) as (relationship_suffix_count agtype, node_suffix_count agtype, tail_node_prefix_count agtype); +relationship_suffix_count|node_suffix_count|tail_node_prefix_count +1|2|1 +1|2|1 +1|2|1 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(relationships(p)[2..]), isEmpty(nodes(p)[3..]), isEmpty(tail(nodes(p))[1..]) $$) as (relationship_suffix_empty agtype, node_suffix_empty agtype, tail_node_suffix_empty agtype); +relationship_suffix_empty|node_suffix_empty|tail_node_suffix_empty +true|true|false +true|true|false +true|true|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(tail(nodes(p))), size(reverse(nodes(p))), size(tail(relationships(p))), size(reverse(relationships(p))) $$) as (tail_node_count agtype, reverse_node_count agtype, tail_relationship_count agtype, reverse_relationship_count agtype); +tail_node_count|reverse_node_count|tail_relationship_count|reverse_relationship_count +2|3|1|2 +2|3|1|2 +2|3|1|2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(tail(nodes(p))), isEmpty(reverse(nodes(p))), isEmpty(tail(relationships(p))), isEmpty(reverse(relationships(p))) $$) as (tail_nodes_empty agtype, reverse_nodes_empty agtype, tail_relationships_empty agtype, reverse_relationships_empty agtype); +tail_nodes_empty|reverse_nodes_empty|tail_relationships_empty|reverse_relationships_empty +false|false|false|false +false|false|false|false +false|false|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(nodes(p)).id, last(nodes(p)).id, head(relationships(p)).id, last(relationships(p)).id $$) as (first_node agtype, last_node agtype, first_relationship agtype, last_relationship agtype); +first_node|last_node|first_relationship|last_relationship +||| +||0|1 +||2|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(nodes(p)[1]), id(relationships(p)[1]) $$) as (node_id agtype, relationship_id agtype); +node_id|relationship_id +281474976710660|844424930131970 +281474976710663|844424930131972 +281474976710666|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(relationships(p)[1])), id(endNode(relationships(p)[1])) $$) as (start_node_id agtype, end_node_id agtype); +start_node_id|end_node_id +281474976710660|281474976710661 +281474976710663|281474976710664 +281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)))), id(endNode(head(reverse(relationships(p))))) $$) as (head_start_id agtype, reverse_end_id agtype); +head_start_id|reverse_end_id +281474976710659|281474976710661 +281474976710662|281474976710664 +281474976710665|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(relationships(p)[1]), endNode(relationships(p)[1]) $$) as (start_node agtype, end_node agtype); +start_node|end_node +{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710663, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710666, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(head(relationships(p))), endNode(head(reverse(relationships(p)))) $$) as (head_start_node agtype, reverse_end_node agtype); +head_start_node|reverse_end_node +{"id": 281474976710659, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710662, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex +{"id": 281474976710665, "label": "_ag_label_vertex", "properties": {}}::vertex|{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(startNode(relationships(p)[1])), labels(endNode(head(relationships(p)))), properties(startNode(reverse(relationships(p))[0])) $$) as (endpoint_label agtype, endpoint_labels agtype, endpoint_properties agtype); +endpoint_label|endpoint_labels|endpoint_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(nodes(p)[1]), labels(nodes(p)[1]), properties(nodes(p)[1]) $$) as (node_label agtype, node_labels agtype, node_properties agtype); +node_label|node_labels|node_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p))), labels(head(tail(nodes(p)))), properties(head(reverse(nodes(p)))) $$) as (head_node_label agtype, tail_node_labels agtype, reverse_node_properties agtype); +head_node_label|tail_node_labels|reverse_node_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(relationships(p)[1]), label(relationships(p)[1]) $$) as (edge_type agtype, edge_label agtype); +edge_type|edge_label +"knows"|"knows" +"knows"|"knows" +"knows"|"knows" +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN properties(relationships(p)[1]) $$) as (edge_properties agtype); +edge_properties +{} +{"id": 1} +{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(relationships(p))), label(head(reverse(relationships(p)))), properties(head(tail(relationships(p)))) $$) as (head_edge_type agtype, reverse_edge_label agtype, tail_edge_properties agtype); +head_edge_type|reverse_edge_label|tail_edge_properties +"knows"|"knows"|{} +"knows"|"knows"|{"id": 1} +"knows"|"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN nodes(p)[1..] $$) as (node_suffix agtype); +node_suffix +[{"id": 281474976710660, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 281474976710663, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 281474976710666, "label": "_ag_label_vertex", "properties": {}}::vertex, {"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(nodes(p))).id, head(reverse(nodes(p))).id $$) as (tail_first_node agtype, reverse_first_node agtype); +tail_first_node|reverse_first_node +| +| +| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(nodes(p))), id(head(tail(nodes(p)))), id(head(reverse(nodes(p)))) $$) as (first_node_id agtype, tail_node_id agtype, reverse_node_id agtype); +first_node_id|tail_node_id|reverse_node_id +281474976710659|281474976710660|281474976710661 +281474976710662|281474976710663|281474976710664 +281474976710665|281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(last(tail(nodes(p)))), count(last(tail(relationships(p)))) $$) as (tail_last_node_count agtype, tail_last_relationship_count agtype); +tail_last_node_count|tail_last_relationship_count +7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(id(last(tail(nodes(p))))), count(id(last(tail(relationships(p))))) $$) as (tail_last_node_id_count agtype, tail_last_relationship_id_count agtype); +tail_last_node_id_count|tail_last_relationship_id_count +7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(label(last(tail(nodes(p))))), count(properties(last(tail(nodes(p))))), count(type(last(tail(relationships(p))))) $$) as (tail_last_node_label_count agtype, tail_last_node_properties_count agtype, tail_last_relationship_type_count agtype); +tail_last_node_label_count|tail_last_node_properties_count|tail_last_relationship_type_count +7|7|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(startNode(last(tail(relationships(p))))), count(endNode(last(tail(relationships(p))))), count(id(startNode(last(tail(relationships(p)))))), count(id(endNode(last(tail(relationships(p)))))) $$) as (tail_last_start_node_count agtype, tail_last_end_node_count agtype, tail_last_start_id_count agtype, tail_last_end_id_count agtype); +tail_last_start_node_count|tail_last_end_node_count|tail_last_start_id_count|tail_last_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(label(startNode(last(tail(relationships(p)))))), count(labels(endNode(last(tail(relationships(p)))))), count(properties(startNode(last(tail(relationships(p)))))) $$) as (tail_last_start_label_count agtype, tail_last_end_labels_count agtype, tail_last_start_properties_count agtype); +tail_last_start_label_count|tail_last_end_labels_count|tail_last_start_properties_count +3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(tail(nodes(p))[0]), id(tail(relationships(p))[0]) $$) as (tail_node0_id agtype, tail_relationship0_id agtype); +tail_node0_id|tail_relationship0_id +281474976710660|844424930131970 +281474976710663|844424930131972 +281474976710666|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(reverse(nodes(p))[0]), id(reverse(relationships(p))[0]) $$) as (reverse_node0_id agtype, reverse_relationship0_id agtype); +reverse_node0_id|reverse_relationship0_id +281474976710661|844424930131970 +281474976710664|844424930131972 +281474976710667|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(reverse(nodes(p))[0]), properties(reverse(nodes(p))[0]), type(reverse(relationships(p))[0]), properties(reverse(relationships(p))[0]) $$) as (reverse_node0_label agtype, reverse_node0_properties agtype, reverse_relationship0_type agtype, reverse_relationship0_properties agtype); +reverse_node0_label|reverse_node0_properties|reverse_relationship0_type|reverse_relationship0_properties +"_ag_label_vertex"|{}|"knows"|{} +"_ag_label_vertex"|{}|"knows"|{"id": 1} +"_ag_label_vertex"|{}|"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(reverse(relationships(p))[0])), id(endNode(reverse(relationships(p))[0])) $$) as (reverse_relationship0_start_id agtype, reverse_relationship0_end_id agtype); +reverse_relationship0_start_id|reverse_relationship0_end_id +281474976710660|281474976710661 +281474976710663|281474976710664 +281474976710666|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(reverse(nodes(p))[0..1])), id(head(reverse(relationships(p))[0..1])) $$) as (reverse_node_slice_head_id agtype, reverse_relationship_slice_head_id agtype); +reverse_node_slice_head_id|reverse_relationship_slice_head_id +281474976710661|844424930131970 +281474976710664|844424930131972 +281474976710667|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(reverse(relationships(p))[0..1])), properties(head(reverse(relationships(p))[0..1])) $$) as (reverse_relationship_slice_head_type agtype, reverse_relationship_slice_head_properties agtype); +reverse_relationship_slice_head_type|reverse_relationship_slice_head_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN reverse(relationships(p))[0..1], reverse(nodes(p))[0..1] $$) as (reverse_relationship_slice agtype, reverse_node_slice agtype); +reverse_relationship_slice|reverse_node_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge]|[{"id": 281474976710661, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge]|[{"id": 281474976710664, "label": "_ag_label_vertex", "properties": {}}::vertex] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge]|[{"id": 281474976710667, "label": "_ag_label_vertex", "properties": {}}::vertex] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).id $$) as (tail_first_relationship agtype, reverse_first_relationship agtype); +tail_first_relationship|reverse_first_relationship +| +1|1 +3|3 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p))), id(head(tail(relationships(p)))), id(head(reverse(relationships(p)))) $$) as (first_relationship_id agtype, tail_relationship_id agtype, reverse_relationship_id agtype); +first_relationship_id|tail_relationship_id|reverse_relationship_id +844424930131971|844424930131970|844424930131970 +844424930131973|844424930131972|844424930131972 +844424930131975|844424930131974|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).arry[2].stats $$) as (tail_first_relationship_id agtype, reverse_relationship_stats agtype); +tail_first_relationship_id|reverse_relationship_stats +| +1| +3|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1..] $$) as (relationship_suffix agtype); +relationship_suffix +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0..1] $$) as (edge_slice agtype); +edge_slice +[{"id": 844424930131971, "label": "knows", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge] +[{"id": 844424930131973, "label": "knows", "end_id": 281474976710663, "start_id": 281474976710662, "properties": {"id": 0}}::edge] +[{"id": 844424930131975, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {"id": 2, "arry": [0, 1, 2, 3, {"name": "joe"}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[-1..] $$) as (edge_slice agtype); +edge_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e), isEmpty(e) $$) as (edge_size agtype, is_empty agtype); +edge_size|is_empty +2|false +2|false +2|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e)), id(last(e)), type(head(e)), id(startNode(last(e))) $$) as (head_edge_id agtype, last_edge_id agtype, head_edge_type agtype, last_edge_start_id agtype); +head_edge_id|last_edge_id|head_edge_type|last_edge_start_id +844424930131971|844424930131970|"knows"|281474976710660 +844424930131973|844424930131972|"knows"|281474976710663 +844424930131975|844424930131974|"knows"|281474976710666 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).id, last(e).id, last(e).arry[2].stats $$) as (head_edge_id agtype, last_edge_id agtype, last_edge_stats agtype); +head_edge_id|last_edge_id|last_edge_stats +|| +0|1| +2|3|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).label, last(e).start_id, last(e).end_id $$) as (head_edge_label agtype, last_edge_start_id agtype, last_edge_end_id agtype); +head_edge_label|last_edge_start_id|last_edge_end_id +|| +|| +|| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e[0..1]), size(e[-1..]), size(tail(e)[0..1]) $$) as (edge_prefix_count agtype, edge_suffix_count agtype, tail_edge_prefix_count agtype); +edge_prefix_count|edge_suffix_count|tail_edge_prefix_count +1|1|1 +1|1|1 +1|1|1 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(e[0..0]), isEmpty(e[0..1]), isEmpty(tail(e)[1..]) $$) as (edge_empty_prefix agtype, edge_nonempty_prefix agtype, tail_edge_suffix_empty agtype); +edge_empty_prefix|edge_nonempty_prefix|tail_edge_suffix_empty +true|false|true +true|false|true +true|false|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e[0..1])), id(last(e[-1..])), id(head(tail(e)[0..1])) $$) as (edge_slice_head_id agtype, edge_slice_last_id agtype, tail_edge_slice_head_id agtype); +edge_slice_head_id|edge_slice_last_id|tail_edge_slice_head_id +844424930131971|844424930131970|844424930131970 +844424930131973|844424930131972|844424930131972 +844424930131975|844424930131974|844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p)[1..])), id(last(nodes(p)[..2])), id(head(tail(nodes(p))[0..1])) $$) as (relationship_slice_head_id agtype, node_slice_last_id agtype, tail_node_slice_head_id agtype); +relationship_slice_head_id|node_slice_last_id|tail_node_slice_head_id +844424930131970|281474976710660|281474976710660 +844424930131972|281474976710663|281474976710663 +844424930131974|281474976710666|281474976710666 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(head(e[0..1])), properties(last(e[-1..])) $$) as (edge_slice_head_type agtype, edge_slice_last_properties agtype); +edge_slice_head_type|edge_slice_last_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p)[1..])), labels(last(tail(nodes(p))[0..1])), properties(last(nodes(p)[..2])) $$) as (node_slice_head_label agtype, tail_node_slice_last_labels agtype, node_slice_last_properties agtype); +node_slice_head_label|tail_node_slice_last_labels|node_slice_last_properties +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +"_ag_label_vertex"|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last(e[-1..]).arry[2].stats $$) as (edge_slice_last_stats agtype); +edge_slice_last_stats + + +{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(head(e[0..1]))), id(endNode(last(e[-1..]))) $$) as (edge_slice_head_start_id agtype, edge_slice_last_end_id agtype); +edge_slice_head_start_id|edge_slice_last_end_id +281474976710659|281474976710661 +281474976710662|281474976710664 +281474976710665|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)[1..]))), labels(endNode(last(relationships(p)[1..]))), properties(startNode(head(relationships(p)[1..]))) $$) as (rel_slice_head_start_id agtype, rel_slice_last_end_labels agtype, rel_slice_head_start_properties agtype); +rel_slice_head_start_id|rel_slice_last_end_labels|rel_slice_head_start_properties +281474976710660|["_ag_label_vertex"]|{} +281474976710663|["_ag_label_vertex"]|{} +281474976710666|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(e)), size(reverse(e)) $$) as (tail_edge_size agtype, reverse_edge_size agtype); +tail_edge_size|reverse_edge_size +1|2 +1|2 +1|2 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(tail(e)), isEmpty(reverse(e)) $$) as (tail_edge_empty agtype, reverse_edge_empty agtype); +tail_edge_empty|reverse_edge_empty +false|false +false|false +false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))), size(reverse(tail(e))), isEmpty(tail(reverse(e))), isEmpty(reverse(tail(e))) $$) as (tail_reverse_edge_size agtype, reverse_tail_edge_size agtype, tail_reverse_edge_empty agtype, reverse_tail_edge_empty agtype); +tail_reverse_edge_size|reverse_tail_edge_size|tail_reverse_edge_empty|reverse_tail_edge_empty +1|1|false|false +1|1|false|false +1|1|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))), isEmpty(tail(tail(e))) $$) as (double_tail_edge_size agtype, double_tail_edge_empty agtype); +double_tail_edge_size|double_tail_edge_empty +0|true +0|true +0|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(tail(e))[0..1] $$) as (double_tail_edge_slice agtype); +double_tail_edge_slice +[] +[] +[] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(tail(nodes(p)))[0..1])), count(head(tail(tail(relationships(p)))[0..1])) $$) as (double_tail_node_slice_count agtype, double_tail_rel_slice_count agtype); +double_tail_node_slice_count|double_tail_rel_slice_count +3|0 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))[0..1]), isEmpty(tail(tail(e))[0..1]) $$) as (double_tail_edge_slice_size agtype, double_tail_edge_slice_empty agtype); +double_tail_edge_slice_size|double_tail_edge_slice_empty +0|true +0|true +0|true +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN count(startNode(e[0])), count(endNode(e[0])), count(id(startNode(e[0]))), count(id(endNode(e[0]))) $$) as (edge_start_node_count agtype, edge_end_node_count agtype, edge_start_id_count agtype, edge_end_id_count agtype); +edge_start_node_count|edge_end_node_count|edge_start_id_count|edge_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(e)[0]) $$) as (tail_edge0_id agtype); +tail_edge0_id +844424930131970 +844424930131972 +844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(reverse(e)[0]) $$) as (reverse_edge0_id agtype); +reverse_edge0_id +844424930131970 +844424930131972 +844424930131974 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(reverse(e)[0]), properties(reverse(e)[0]) $$) as (reverse_edge0_type agtype, reverse_edge0_properties agtype); +reverse_edge0_type|reverse_edge0_properties +"knows"|{} +"knows"|{"id": 1} +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(reverse(e)[0..1])), head(reverse(e)[0..1]).arry[2].stats, id(endNode(head(reverse(e)[0..1]))) $$) as (reverse_edge_slice_head_id agtype, reverse_edge_slice_stats agtype, reverse_edge_slice_end_id agtype); +reverse_edge_slice_head_id|reverse_edge_slice_stats|reverse_edge_slice_end_id +844424930131970||281474976710661 +844424930131972||281474976710664 +844424930131974|{"age": 1000}|281474976710667 +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e)[0..1], reverse(e)[0..1] $$) as (tail_edge_slice agtype, reverse_edge_slice agtype); +tail_edge_slice|reverse_edge_slice +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge]|[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge]|[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge]|[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0..1], reverse(tail(e))[0..1] $$) as (tail_reverse_edge_slice agtype, reverse_tail_edge_slice agtype); +tail_reverse_edge_slice|reverse_tail_edge_slice +[{"id": 844424930131971, "label": "knows", "end_id": 281474976710660, "start_id": 281474976710659, "properties": {}}::edge]|[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131973, "label": "knows", "end_id": 281474976710663, "start_id": 281474976710662, "properties": {"id": 0}}::edge]|[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131975, "label": "knows", "end_id": 281474976710666, "start_id": 281474976710665, "properties": {"id": 2, "arry": [0, 1, 2, 3, {"name": "joe"}]}}::edge]|[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(reverse(nodes(p)))[0..1])), count(head(reverse(tail(nodes(p)))[0..1])), count(head(tail(reverse(relationships(p)))[0..1])), count(head(reverse(tail(relationships(p)))[0..1])) $$) as (tail_reverse_node_slice_count agtype, reverse_tail_node_slice_count agtype, tail_reverse_rel_slice_count agtype, reverse_tail_rel_slice_count agtype); +tail_reverse_node_slice_count|reverse_tail_node_slice_count|tail_reverse_rel_slice_count|reverse_tail_rel_slice_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))[0..1]), size(reverse(tail(e))[0..1]), isEmpty(tail(reverse(e))[0..1]), isEmpty(reverse(tail(e))[0..1]) $$) as (tail_reverse_edge_slice_size agtype, reverse_tail_edge_slice_size agtype, tail_reverse_edge_slice_empty agtype, reverse_tail_edge_slice_empty agtype); +tail_reverse_edge_slice_size|reverse_tail_edge_slice_size|tail_reverse_edge_slice_empty|reverse_tail_edge_slice_empty +1|1|false|false +1|1|false|false +1|1|false|false +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(tail(reverse(e))[0..1])), id(last(reverse(tail(e))[0..1])), id(head(tail(tail(e))[0..1])) $$) as (tail_reverse_slice_head_id agtype, reverse_tail_slice_last_id agtype, double_tail_slice_head_id agtype); +tail_reverse_slice_head_id|reverse_tail_slice_last_id|double_tail_slice_head_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(last(tail(reverse(e)))), id(head(reverse(tail(e)))), last(tail(tail(e))).id $$) as (tail_reverse_last_id agtype, reverse_tail_head_id agtype, double_tail_last_id agtype); +tail_reverse_last_id|reverse_tail_head_id|double_tail_last_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(last(tail(reverse(e)))), properties(head(reverse(tail(e)))), head(reverse(tail(e))).arry[2].stats $$) as (tail_reverse_last_type agtype, reverse_tail_head_properties agtype, reverse_tail_head_stats agtype); +tail_reverse_last_type|reverse_tail_head_properties|reverse_tail_head_stats +"knows"|{}| +"knows"|{"id": 1}| +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}|{"age": 1000} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(last(tail(reverse(e))))), id(endNode(head(reverse(tail(e))))), labels(startNode(head(reverse(tail(e))))), properties(endNode(last(tail(reverse(e))))) $$) as (tail_reverse_last_start_id agtype, reverse_tail_head_end_id agtype, reverse_tail_head_start_labels agtype, tail_reverse_last_end_properties agtype); +tail_reverse_last_start_id|reverse_tail_head_end_id|reverse_tail_head_start_labels|tail_reverse_last_end_properties +281474976710659|281474976710661|["_ag_label_vertex"]|{} +281474976710662|281474976710664|["_ag_label_vertex"]|{} +281474976710665|281474976710667|["_ag_label_vertex"]|{} +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(reverse(e))[0]), id(reverse(tail(e))[0]), id(tail(tail(e))[0]) $$) as (tail_reverse_index_id agtype, reverse_tail_index_id agtype, double_tail_index_id agtype); +tail_reverse_index_id|reverse_tail_index_id|double_tail_index_id +844424930131971|844424930131970| +844424930131973|844424930131972| +844424930131975|844424930131974| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(tail(reverse(e))[0]), properties(reverse(tail(e))[0]), properties(tail(tail(e))[0]) $$) as (tail_reverse_index_type agtype, reverse_tail_index_properties agtype, double_tail_index_properties agtype); +tail_reverse_index_type|reverse_tail_index_properties|double_tail_index_properties +"knows"|{}| +"knows"|{"id": 1}| +"knows"|{"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(tail(reverse(e))[0])), id(endNode(reverse(tail(e))[0])), labels(startNode(tail(tail(e))[0])) $$) as (tail_reverse_start_id agtype, reverse_tail_end_id agtype, double_tail_start_labels agtype); +tail_reverse_start_id|reverse_tail_end_id|double_tail_start_labels +281474976710659|281474976710661| +281474976710662|281474976710664| +281474976710665|281474976710667| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN reverse(tail(e))[0].arry[2].stats, tail(reverse(e))[0].id, tail(tail(e))[0].id $$) as (reverse_tail_index_stats agtype, tail_reverse_index_property_id agtype, double_tail_index_property_id agtype); +reverse_tail_index_stats|tail_reverse_index_property_id|double_tail_index_property_id +|| +|0| +{"age": 1000}|2| +(3 rows) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(last(tail(e))) $$) as (tail_last_edge_count agtype); +tail_last_edge_count +3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(id(last(tail(e)))) $$) as (tail_last_edge_id_count agtype); +tail_last_edge_id_count +3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(type(last(tail(e)))), count(properties(last(tail(e)))), count(last(tail(e)).arry[2].stats) $$) as (tail_last_edge_type_count agtype, tail_last_edge_properties_count agtype, tail_last_edge_property_count agtype); +tail_last_edge_type_count|tail_last_edge_properties_count|tail_last_edge_property_count +3|3|1 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e)))), count(endNode(last(tail(e)))), count(id(startNode(last(tail(e))))), count(id(endNode(last(tail(e))))) $$) as (tail_last_edge_start_node_count agtype, tail_last_edge_end_node_count agtype, tail_last_edge_start_id_count agtype, tail_last_edge_end_id_count agtype); +tail_last_edge_start_node_count|tail_last_edge_end_node_count|tail_last_edge_start_id_count|tail_last_edge_end_id_count +3|3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(label(startNode(last(tail(e))))), count(labels(endNode(last(tail(e))))), count(properties(endNode(last(tail(e))))) $$) as (tail_last_edge_start_label_count agtype, tail_last_edge_end_labels_count agtype, tail_last_edge_end_properties_count agtype); +tail_last_edge_start_label_count|tail_last_edge_end_labels_count|tail_last_edge_end_properties_count +3|3|3 +(1 row) +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e) $$) as (edge_tail agtype); +edge_tail +[{"id": 844424930131970, "label": "knows", "end_id": 281474976710661, "start_id": 281474976710660, "properties": {}}::edge] +[{"id": 844424930131972, "label": "knows", "end_id": 281474976710664, "start_id": 281474976710663, "properties": {"id": 1}}::edge] +[{"id": 844424930131974, "label": "knows", "end_id": 281474976710667, "start_id": 281474976710666, "properties": {"id": 3, "arry": [1, 3, {"name": "john", "stats": {"age": 1000}}]}}::edge] +(3 rows) +\pset format aligned SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN properties(e[0]), properties(e[1]) $$) as (prop_1st agtype, prop_2nd agtype); prop_1st | prop_2nd ---------------------------------------------------------------------+--------------------------------------------------------------------- @@ -1098,6 +1685,32 @@ SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*2..2]-({name: "Jane Doe" (1 row) +\pset format unaligned +SELECT * FROM cypher('issue_1910', $$ MATCH p=()-[*1..1]->() RETURN count(startNode(relationships(p)[0]).name) + count(endNode(relationships(p)[0]).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(e[0]).name) + count(endNode(e[0]).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e))).name) + count(endNode(last(tail(e))).name) $$) AS (c agtype); +c +2 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(head(e[0..1])).name) + count(endNode(last(e[0..])).name) $$) AS (c agtype); +c +6 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(tail(reverse(e))[0]).name) + count(endNode(reverse(tail(e))[0]).name) $$) AS (c agtype); +c +2 +(1 row) +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(reverse(e)))).name) + count(endNode(head(reverse(tail(e)))).name) $$) AS (c agtype); +c +2 +(1 row) +\pset format aligned SELECT drop_graph('issue_1910', true); NOTICE: drop cascades to 3 other objects DETAIL: drop cascades to table issue_1910._ag_label_vertex diff --git a/regress/sql/cypher_vle.sql b/regress/sql/cypher_vle.sql index c960aa7a4..483ff2143 100644 --- a/regress/sql/cypher_vle.sql +++ b/regress/sql/cypher_vle.sql @@ -104,6 +104,25 @@ SELECT * FROM cypher('cypher_vle', $$MATCH ()<-[*4..4 {name: "main edge"}]-(v) R SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->() RETURN count(*) $$) AS (e agtype); SELECT * FROM cypher('cypher_vle', $$MATCH (u)-[*]->() RETURN count(*) $$) AS (e agtype); SELECT * FROM cypher('cypher_vle', $$MATCH ()-[*]->(v) RETURN count(*) $$) AS (e agtype); +\pset format unaligned +-- Empty direct age_vle finite range should not traverse +SELECT count(edges) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '2'::agtype, '1'::agtype); +-- Direct runtime nodes()/relationships() should project compact VLE paths +SELECT count(age_nodes(edges)), count(age_relationships(edges)), sum((age_length(edges))::text::int) FROM start_and_end_points, age_vle( '"cypher_vle"'::agtype, start_vertex, end_vertex, '{"id": 1111111111111111, "label": "", "end_id": 2222222222222222, "start_id": 333333333333333, "properties": {}}::edge'::agtype, '3'::agtype, '3'::agtype, '1'::agtype); +-- Optimized anonymous terminal: bound start, unused anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH p=(u:begin)-[e*1..2 {name: "main edge"}]->() RETURN count(p) $$) AS (e agtype); +-- Optimized right-bound paths-to: anonymous start, selective anonymous end +SELECT * FROM cypher('cypher_vle', $$MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH p=()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(p) $$) AS (plan agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH ()-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*1..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +-- Optimized two-bound lower-zero VLE: age_vle handles start=end zero-path semantics. +SELECT * FROM cypher('cypher_vle', $$MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)-[e*0..2 {name: "main edge"}]->(u) RETURN count(*) $$) AS (e agtype); +SELECT * FROM cypher('cypher_vle', $$EXPLAIN (COSTS OFF) MATCH (:begin)-[e*0..2 {name: "main edge"}]->(:end) RETURN count(*) $$) AS (plan agtype); +\pset format aligned -- Should find 2 SELECT * FROM cypher('cypher_vle', $$MATCH (u:begin)<-[e*]-(v:end) RETURN e $$) AS (e agtype); -- Should find 5 @@ -322,6 +341,98 @@ SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].id $$) as (re SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].arry[2] $$) as (results agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[1].arry[2].stats $$) as (results agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN properties(e[2]) $$) as (prop_third_edge agtype); +\pset format unaligned +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN e[1].arry[2].stats, tail(e)[0].id, relationships(p)[1].id $$) as (edge_index_stats agtype, tail_index_id agtype, relationship_index_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(e[0]), label(e[0]) $$) as (edge_type agtype, edge_label agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0] = e[0], e[0] = e[1] $$) as (same_edge agtype, different_edge agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN head(e) IN e, tail(e)[0] IN e, relationships(p)[1] IN relationships(p), last(e) IN e $$) as (head_in_edges agtype, tail_index_in_edges agtype, relationship_index_in_path agtype, last_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last([e[0]]) = head(e), head([tail(e)[0]]) = last(e), last([e[1]]) IN e $$) as (singleton_head_equal agtype, singleton_tail_equal agtype, singleton_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] IN e, reverse(tail(e))[0] IN e, tail(tail(e))[0] IN e $$) as (tail_reverse_in_edges agtype, reverse_tail_in_edges agtype, double_tail_in_edges agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[e*2..2]->() RETURN tail(reverse(e))[0] = e[0], reverse(tail(e))[0] = e[1], tail(tail(e))[0] = e[0], reverse(tail(e))[0] = relationships(p)[1] $$) as (nested_tail_reverse_same agtype, nested_reverse_tail_same agtype, nested_double_tail_same agtype, nested_path_relationship_same agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0] = reverse(tail(e))[0], tail(reverse(e))[0] = tail(reverse(e))[0], reverse(tail(e))[0] = tail(reverse(e))[0], tail(tail(e))[0] = tail(tail(e))[0] $$) as (nested_cross_same agtype, nested_tail_reverse_self agtype, nested_cross_symmetric agtype, nested_empty_self agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1].id $$) as (relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)) $$) as (relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(relationships(p)[1..]), size(nodes(p)[1..]), size(tail(nodes(p))[0..1]) $$) as (relationship_suffix_count agtype, node_suffix_count agtype, tail_node_prefix_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(relationships(p)[2..]), isEmpty(nodes(p)[3..]), isEmpty(tail(nodes(p))[1..]) $$) as (relationship_suffix_empty agtype, node_suffix_empty agtype, tail_node_suffix_empty agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN size(tail(nodes(p))), size(reverse(nodes(p))), size(tail(relationships(p))), size(reverse(relationships(p))) $$) as (tail_node_count agtype, reverse_node_count agtype, tail_relationship_count agtype, reverse_relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN isEmpty(tail(nodes(p))), isEmpty(reverse(nodes(p))), isEmpty(tail(relationships(p))), isEmpty(reverse(relationships(p))) $$) as (tail_nodes_empty agtype, reverse_nodes_empty agtype, tail_relationships_empty agtype, reverse_relationships_empty agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(nodes(p)).id, last(nodes(p)).id, head(relationships(p)).id, last(relationships(p)).id $$) as (first_node agtype, last_node agtype, first_relationship agtype, last_relationship agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(nodes(p)[1]), id(relationships(p)[1]) $$) as (node_id agtype, relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(relationships(p)[1])), id(endNode(relationships(p)[1])) $$) as (start_node_id agtype, end_node_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)))), id(endNode(head(reverse(relationships(p))))) $$) as (head_start_id agtype, reverse_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(relationships(p)[1]), endNode(relationships(p)[1]) $$) as (start_node agtype, end_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN startNode(head(relationships(p))), endNode(head(reverse(relationships(p)))) $$) as (head_start_node agtype, reverse_end_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(startNode(relationships(p)[1])), labels(endNode(head(relationships(p)))), properties(startNode(reverse(relationships(p))[0])) $$) as (endpoint_label agtype, endpoint_labels agtype, endpoint_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(nodes(p)[1]), labels(nodes(p)[1]), properties(nodes(p)[1]) $$) as (node_label agtype, node_labels agtype, node_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p))), labels(head(tail(nodes(p)))), properties(head(reverse(nodes(p)))) $$) as (head_node_label agtype, tail_node_labels agtype, reverse_node_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(relationships(p)[1]), label(relationships(p)[1]) $$) as (edge_type agtype, edge_label agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN properties(relationships(p)[1]) $$) as (edge_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(relationships(p))), label(head(reverse(relationships(p)))), properties(head(tail(relationships(p)))) $$) as (head_edge_type agtype, reverse_edge_label agtype, tail_edge_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN nodes(p)[1..] $$) as (node_suffix agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(nodes(p))).id, head(reverse(nodes(p))).id $$) as (tail_first_node agtype, reverse_first_node agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(nodes(p))), id(head(tail(nodes(p)))), id(head(reverse(nodes(p)))) $$) as (first_node_id agtype, tail_node_id agtype, reverse_node_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(last(tail(nodes(p)))), count(last(tail(relationships(p)))) $$) as (tail_last_node_count agtype, tail_last_relationship_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(id(last(tail(nodes(p))))), count(id(last(tail(relationships(p))))) $$) as (tail_last_node_id_count agtype, tail_last_relationship_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*0..1]->() RETURN count(label(last(tail(nodes(p))))), count(properties(last(tail(nodes(p))))), count(type(last(tail(relationships(p))))) $$) as (tail_last_node_label_count agtype, tail_last_node_properties_count agtype, tail_last_relationship_type_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(startNode(last(tail(relationships(p))))), count(endNode(last(tail(relationships(p))))), count(id(startNode(last(tail(relationships(p)))))), count(id(endNode(last(tail(relationships(p)))))) $$) as (tail_last_start_node_count agtype, tail_last_end_node_count agtype, tail_last_start_id_count agtype, tail_last_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*1..2]->() RETURN count(label(startNode(last(tail(relationships(p)))))), count(labels(endNode(last(tail(relationships(p)))))), count(properties(startNode(last(tail(relationships(p)))))) $$) as (tail_last_start_label_count agtype, tail_last_end_labels_count agtype, tail_last_start_properties_count agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(tail(nodes(p))[0]), id(tail(relationships(p))[0]) $$) as (tail_node0_id agtype, tail_relationship0_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(reverse(nodes(p))[0]), id(reverse(relationships(p))[0]) $$) as (reverse_node0_id agtype, reverse_relationship0_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(reverse(nodes(p))[0]), properties(reverse(nodes(p))[0]), type(reverse(relationships(p))[0]), properties(reverse(relationships(p))[0]) $$) as (reverse_node0_label agtype, reverse_node0_properties agtype, reverse_relationship0_type agtype, reverse_relationship0_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(reverse(relationships(p))[0])), id(endNode(reverse(relationships(p))[0])) $$) as (reverse_relationship0_start_id agtype, reverse_relationship0_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(reverse(nodes(p))[0..1])), id(head(reverse(relationships(p))[0..1])) $$) as (reverse_node_slice_head_id agtype, reverse_relationship_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN type(head(reverse(relationships(p))[0..1])), properties(head(reverse(relationships(p))[0..1])) $$) as (reverse_relationship_slice_head_type agtype, reverse_relationship_slice_head_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN reverse(relationships(p))[0..1], reverse(nodes(p))[0..1] $$) as (reverse_relationship_slice agtype, reverse_node_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).id $$) as (tail_first_relationship agtype, reverse_first_relationship agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p))), id(head(tail(relationships(p)))), id(head(reverse(relationships(p)))) $$) as (first_relationship_id agtype, tail_relationship_id agtype, reverse_relationship_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN head(tail(relationships(p))).id, head(reverse(relationships(p))).arry[2].stats $$) as (tail_first_relationship_id agtype, reverse_relationship_stats agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN relationships(p)[1..] $$) as (relationship_suffix agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[0..1] $$) as (edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN e[-1..] $$) as (edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e), isEmpty(e) $$) as (edge_size agtype, is_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e)), id(last(e)), type(head(e)), id(startNode(last(e))) $$) as (head_edge_id agtype, last_edge_id agtype, head_edge_type agtype, last_edge_start_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).id, last(e).id, last(e).arry[2].stats $$) as (head_edge_id agtype, last_edge_id agtype, last_edge_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN head(e).label, last(e).start_id, last(e).end_id $$) as (head_edge_label agtype, last_edge_start_id agtype, last_edge_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(e[0..1]), size(e[-1..]), size(tail(e)[0..1]) $$) as (edge_prefix_count agtype, edge_suffix_count agtype, tail_edge_prefix_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(e[0..0]), isEmpty(e[0..1]), isEmpty(tail(e)[1..]) $$) as (edge_empty_prefix agtype, edge_nonempty_prefix agtype, tail_edge_suffix_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(e[0..1])), id(last(e[-1..])), id(head(tail(e)[0..1])) $$) as (edge_slice_head_id agtype, edge_slice_last_id agtype, tail_edge_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(head(relationships(p)[1..])), id(last(nodes(p)[..2])), id(head(tail(nodes(p))[0..1])) $$) as (relationship_slice_head_id agtype, node_slice_last_id agtype, tail_node_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(head(e[0..1])), properties(last(e[-1..])) $$) as (edge_slice_head_type agtype, edge_slice_last_properties agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN label(head(nodes(p)[1..])), labels(last(tail(nodes(p))[0..1])), properties(last(nodes(p)[..2])) $$) as (node_slice_head_label agtype, tail_node_slice_last_labels agtype, node_slice_last_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN last(e[-1..]).arry[2].stats $$) as (edge_slice_last_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(head(e[0..1]))), id(endNode(last(e[-1..]))) $$) as (edge_slice_head_start_id agtype, edge_slice_last_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN id(startNode(head(relationships(p)[1..]))), labels(endNode(last(relationships(p)[1..]))), properties(startNode(head(relationships(p)[1..]))) $$) as (rel_slice_head_start_id agtype, rel_slice_last_end_labels agtype, rel_slice_head_start_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(e)), size(reverse(e)) $$) as (tail_edge_size agtype, reverse_edge_size agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN isEmpty(tail(e)), isEmpty(reverse(e)) $$) as (tail_edge_empty agtype, reverse_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))), size(reverse(tail(e))), isEmpty(tail(reverse(e))), isEmpty(reverse(tail(e))) $$) as (tail_reverse_edge_size agtype, reverse_tail_edge_size agtype, tail_reverse_edge_empty agtype, reverse_tail_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))), isEmpty(tail(tail(e))) $$) as (double_tail_edge_size agtype, double_tail_edge_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(tail(e))[0..1] $$) as (double_tail_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(tail(nodes(p)))[0..1])), count(head(tail(tail(relationships(p)))[0..1])) $$) as (double_tail_node_slice_count agtype, double_tail_rel_slice_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(tail(e))[0..1]), isEmpty(tail(tail(e))[0..1]) $$) as (double_tail_edge_slice_size agtype, double_tail_edge_slice_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN count(startNode(e[0])), count(endNode(e[0])), count(id(startNode(e[0]))), count(id(endNode(e[0]))) $$) as (edge_start_node_count agtype, edge_end_node_count agtype, edge_start_id_count agtype, edge_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(e)[0]) $$) as (tail_edge0_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(reverse(e)[0]) $$) as (reverse_edge0_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(reverse(e)[0]), properties(reverse(e)[0]) $$) as (reverse_edge0_type agtype, reverse_edge0_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(reverse(e)[0..1])), head(reverse(e)[0..1]).arry[2].stats, id(endNode(head(reverse(e)[0..1]))) $$) as (reverse_edge_slice_head_id agtype, reverse_edge_slice_stats agtype, reverse_edge_slice_end_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e)[0..1], reverse(e)[0..1] $$) as (tail_edge_slice agtype, reverse_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(reverse(e))[0..1], reverse(tail(e))[0..1] $$) as (tail_reverse_edge_slice agtype, reverse_tail_edge_slice agtype); +SELECT * FROM cypher('access',$$ MATCH p=()-[*2..2]->() RETURN count(head(tail(reverse(nodes(p)))[0..1])), count(head(reverse(tail(nodes(p)))[0..1])), count(head(tail(reverse(relationships(p)))[0..1])), count(head(reverse(tail(relationships(p)))[0..1])) $$) as (tail_reverse_node_slice_count agtype, reverse_tail_node_slice_count agtype, tail_reverse_rel_slice_count agtype, reverse_tail_rel_slice_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN size(tail(reverse(e))[0..1]), size(reverse(tail(e))[0..1]), isEmpty(tail(reverse(e))[0..1]), isEmpty(reverse(tail(e))[0..1]) $$) as (tail_reverse_edge_slice_size agtype, reverse_tail_edge_slice_size agtype, tail_reverse_edge_slice_empty agtype, reverse_tail_edge_slice_empty agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(head(tail(reverse(e))[0..1])), id(last(reverse(tail(e))[0..1])), id(head(tail(tail(e))[0..1])) $$) as (tail_reverse_slice_head_id agtype, reverse_tail_slice_last_id agtype, double_tail_slice_head_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(last(tail(reverse(e)))), id(head(reverse(tail(e)))), last(tail(tail(e))).id $$) as (tail_reverse_last_id agtype, reverse_tail_head_id agtype, double_tail_last_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(last(tail(reverse(e)))), properties(head(reverse(tail(e)))), head(reverse(tail(e))).arry[2].stats $$) as (tail_reverse_last_type agtype, reverse_tail_head_properties agtype, reverse_tail_head_stats agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(last(tail(reverse(e))))), id(endNode(head(reverse(tail(e))))), labels(startNode(head(reverse(tail(e))))), properties(endNode(last(tail(reverse(e))))) $$) as (tail_reverse_last_start_id agtype, reverse_tail_head_end_id agtype, reverse_tail_head_start_labels agtype, tail_reverse_last_end_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(tail(reverse(e))[0]), id(reverse(tail(e))[0]), id(tail(tail(e))[0]) $$) as (tail_reverse_index_id agtype, reverse_tail_index_id agtype, double_tail_index_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN type(tail(reverse(e))[0]), properties(reverse(tail(e))[0]), properties(tail(tail(e))[0]) $$) as (tail_reverse_index_type agtype, reverse_tail_index_properties agtype, double_tail_index_properties agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN id(startNode(tail(reverse(e))[0])), id(endNode(reverse(tail(e))[0])), labels(startNode(tail(tail(e))[0])) $$) as (tail_reverse_start_id agtype, reverse_tail_end_id agtype, double_tail_start_labels agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN reverse(tail(e))[0].arry[2].stats, tail(reverse(e))[0].id, tail(tail(e))[0].id $$) as (reverse_tail_index_stats agtype, tail_reverse_index_property_id agtype, double_tail_index_property_id agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(last(tail(e))) $$) as (tail_last_edge_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(id(last(tail(e)))) $$) as (tail_last_edge_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(type(last(tail(e)))), count(properties(last(tail(e)))), count(last(tail(e)).arry[2].stats) $$) as (tail_last_edge_type_count agtype, tail_last_edge_properties_count agtype, tail_last_edge_property_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e)))), count(endNode(last(tail(e)))), count(id(startNode(last(tail(e))))), count(id(endNode(last(tail(e))))) $$) as (tail_last_edge_start_node_count agtype, tail_last_edge_end_node_count agtype, tail_last_edge_start_id_count agtype, tail_last_edge_end_id_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*1..2]->() RETURN count(label(startNode(last(tail(e))))), count(labels(endNode(last(tail(e))))), count(properties(endNode(last(tail(e))))) $$) as (tail_last_edge_start_label_count agtype, tail_last_edge_end_labels_count agtype, tail_last_edge_end_properties_count agtype); +SELECT * FROM cypher('access',$$ MATCH ()-[e*2..2]->() RETURN tail(e) $$) as (edge_tail agtype); +\pset format aligned SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN properties(e[0]), properties(e[1]) $$) as (prop_1st agtype, prop_2nd agtype); SELECT * FROM cypher('access',$$ MATCH ()-[e*]->() RETURN e[0].id, e[1].id $$) as (results_1st agtype, results_2nd agtype); @@ -355,6 +466,15 @@ SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*1]-({name: 'W SELECT * FROM cypher('issue_1910', $$ MATCH (n) WHERE EXISTS((n)-[*2..2]-({name: 'Willem Defoe'})) RETURN n.name $$) AS (name agtype); +\pset format unaligned +SELECT * FROM cypher('issue_1910', $$ MATCH p=()-[*1..1]->() RETURN count(startNode(relationships(p)[0]).name) + count(endNode(relationships(p)[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(e[0]).name) + count(endNode(e[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(e))).name) + count(endNode(last(tail(e))).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..1]->() RETURN count(startNode(head(e[0..1])).name) + count(endNode(last(e[0..])).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(tail(reverse(e))[0]).name) + count(endNode(reverse(tail(e))[0]).name) $$) AS (c agtype); +SELECT * FROM cypher('issue_1910', $$ MATCH ()-[e*1..2]->() RETURN count(startNode(last(tail(reverse(e)))).name) + count(endNode(head(reverse(tail(e)))).name) $$) AS (c agtype); +\pset format aligned + SELECT drop_graph('issue_1910', true); -- issue 2092: VLE with chained OPTIONAL MATCH and NULL handling diff --git a/sql/agtype_typecast.sql b/sql/agtype_typecast.sql index c29c0a657..459e4e98d 100644 --- a/sql/agtype_typecast.sql +++ b/sql/agtype_typecast.sql @@ -118,6 +118,42 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- function to read the length of a VLE_path_container without materializing it +CREATE FUNCTION ag_catalog.age_vle_path_length(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the number of nodes in a VLE_path_container +CREATE FUNCTION ag_catalog.age_vle_path_node_count(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the number of edges in tail(relationships) for a VLE path +CREATE FUNCTION ag_catalog.age_vle_edge_tail_count(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to test VLE node/edge list emptiness without materializing lists +CREATE FUNCTION ag_catalog.age_vle_list_is_empty(agtype, agtype) + RETURNS boolean + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + -- function to create an AGTV_ARRAY of edges from a VLE_path_container CREATE FUNCTION ag_catalog.age_materialize_vle_edges(agtype) RETURNS agtype @@ -127,6 +163,325 @@ RETURNS NULL ON NULL INPUT PARALLEL SAFE AS 'MODULE_PATHNAME'; +-- function to create an AGTV_ARRAY of nodes from a VLE_path_container +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one vertex from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_materialize_vle_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one vertex from a reversed VLE_path_container node index +CREATE FUNCTION ag_catalog.age_materialize_vle_node_reversed_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the last vertex from a VLE_path_container node-list tail +CREATE FUNCTION ag_catalog.age_materialize_vle_node_tail_last(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the last vertex id from a VLE_path_container node-list tail +CREATE FUNCTION ag_catalog.age_vle_node_tail_last_id(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex id from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex label from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_label_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex labels array from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_labels_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one vertex properties object from a VLE_path_container node index +CREATE FUNCTION ag_catalog.age_vle_node_properties_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a node slice from a VLE_path_container +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_slice(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a VLE list slice without materializing transformed lists +CREATE FUNCTION ag_catalog.age_materialize_vle_list_slice(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to count a VLE list slice without materializing it +CREATE FUNCTION ag_catalog.age_vle_list_slice_count(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to check whether a VLE list slice is empty without materializing it +CREATE FUNCTION ag_catalog.age_vle_list_slice_is_empty(agtype, agtype, agtype, agtype) + RETURNS boolean + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the head/last element of a VLE list slice +CREATE FUNCTION ag_catalog.age_materialize_vle_slice_boundary(agtype, agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the tail of a VLE_path_container node list +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_tail(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a reversed VLE_path_container node list +CREATE FUNCTION ag_catalog.age_materialize_vle_nodes_reversed(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one edge from a VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create one edge from a reversed VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_reversed_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the last edge from a VLE_path_container edge-list tail +CREATE FUNCTION ag_catalog.age_materialize_vle_edge_tail_last(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read the last edge id from a VLE_path_container edge-list tail +CREATE FUNCTION ag_catalog.age_vle_edge_tail_last_id(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one field from the last element of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_field(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an endpoint from the last edge of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_edge_endpoint(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one endpoint field from the last edge of a VLE tail list +CREATE FUNCTION ag_catalog.age_vle_tail_last_endpoint_field(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one edge id from a VLE_path_container relationship index +CREATE FUNCTION ag_catalog.age_vle_edge_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to check whether a VLE edge index resolves to an edge +CREATE FUNCTION ag_catalog.age_vle_edge_index_exists(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to compare two indexed VLE edges by id +CREATE FUNCTION ag_catalog.age_vle_edge_indices_equal(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to compare a reversed-indexed VLE edge with a normal indexed edge +CREATE FUNCTION ag_catalog.age_vle_edge_reversed_index_equal(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's label/type +CREATE FUNCTION ag_catalog.age_vle_edge_label_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's properties +CREATE FUNCTION ag_catalog.age_vle_edge_properties_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create an indexed edge's stored start vertex +CREATE FUNCTION ag_catalog.age_vle_edge_start_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create an indexed edge's stored end vertex +CREATE FUNCTION ag_catalog.age_vle_edge_end_node_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read one indexed edge endpoint field +CREATE FUNCTION ag_catalog.age_vle_edge_endpoint_field_at(agtype, agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's stored start vertex id +CREATE FUNCTION ag_catalog.age_vle_edge_start_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to read an indexed edge's stored end vertex id +CREATE FUNCTION ag_catalog.age_vle_edge_end_id_at(agtype, agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create the tail of a VLE_path_container edge list +CREATE FUNCTION ag_catalog.age_materialize_vle_edges_tail(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + +-- function to create a reversed VLE_path_container edge list +CREATE FUNCTION ag_catalog.age_materialize_vle_edges_reversed(agtype) + RETURNS agtype + LANGUAGE C + STABLE +RETURNS NULL ON NULL INPUT +PARALLEL SAFE +AS 'MODULE_PATHNAME'; + CREATE FUNCTION ag_catalog.age_match_vle_edge_to_id_qual(variadic "any") RETURNS boolean LANGUAGE C diff --git a/src/backend/parser/cypher_clause.c b/src/backend/parser/cypher_clause.c index 441572af7..c2a778d2e 100644 --- a/src/backend/parser/cypher_clause.c +++ b/src/backend/parser/cypher_clause.c @@ -4089,6 +4089,23 @@ static List *make_join_condition_for_edge(cypher_parsestate *cpstate, List *args = NIL; List *quals = NIL; + if (prev_node->in_join_tree && next_node->in_join_tree) + { + FuncCall *vle_func = (FuncCall *)entity->entity.rel->varlen; + Node *start_arg = linitial(vle_func->args); + Node *end_arg = lsecond(vle_func->args); + + /* + * age_vle() already enforces both endpoints when both endpoint + * arguments are bound. Avoid adding a redundant terminal-edge join + * filter that forces path post-filtering. + */ + if (!IsA(start_arg, A_Const) && !IsA(end_arg, A_Const)) + { + return NIL; + } + } + /* * If the next node is not in the join tree, we don't need to make any * quals. @@ -5027,6 +5044,62 @@ static bool isa_special_VLE_case(cypher_path *path) return false; } +static transform_entity *transform_match_node_entity( + cypher_parsestate *cpstate, Query *query, cypher_node *node, + bool output_node, bool valid_label) +{ + ParseState *pstate = (ParseState *)cpstate; + Expr *expr = NULL; + transform_entity *entity = NULL; + + expr = transform_cypher_node(cpstate, node, &query->targetList, + output_node, valid_label); + + entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, expr); + + /* + * Add the entity before transforming property constraints so property + * expressions that reference this same entity can be resolved. + */ + cpstate->entities = lappend(cpstate->entities, entity); + + if (node->props) + { + Node *n = NULL; + Node *prop_var = NULL; + Node *prop_expr = NULL; + + if (node->name != NULL) + { + prop_var = colNameToVar(pstate, node->name, false, + node->location); + } + + if (prop_var != NULL && + pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, + sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) + { + prop_expr = prop_var; + } + else if (prop_var != NULL) + { + prop_expr = make_age_properties_func_expr(prop_var); + } + + if (is_ag_node(node->props, cypher_map)) + { + ((cypher_map*)node->props)->keep_null = true; + } + n = create_property_constraints(cpstate, entity, node->props, + prop_expr); + + cpstate->property_constraint_quals = + lappend(cpstate->property_constraint_quals, n); + } + + return entity; +} + /* * Iterate through the path and construct all edges and necessary vertices */ @@ -5040,6 +5113,8 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, bool node_declared_in_prev_clause = false; transform_entity *prev_entity = NULL; bool special_VLE_case = false; + bool skip_pretransformed_node = false; + transform_entity *pretransformed_node = NULL; special_VLE_case = isa_special_VLE_case(path); @@ -5062,6 +5137,15 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, node = lfirst(lc); + if (skip_pretransformed_node) + { + prev_entity = pretransformed_node; + pretransformed_node = NULL; + skip_pretransformed_node = false; + i++; + continue; + } + /* * The vle needs to know if the start vertex was * created in a previous clause. Check to see if it @@ -5114,71 +5198,84 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, */ output_node = true; - /* transform vertex */ - expr = transform_cypher_node(cpstate, node, &query->targetList, - output_node, valid_label); - - entity = make_transform_entity(cpstate, ENT_VERTEX, (Node *)node, - expr); - /* - * We want to add transformed entity to entities before transforming props - * so that props referencing currently transformed entity can be resolved. + * A terminal anonymous node after a VLE does not add bindings or + * filters. Keeping it in the join tree forces a full vertex scan + * plus age_match_vle_terminal_edge(), even though age_vle() has + * already generated valid terminal vertices for paths-from + * traversal. */ - cpstate->entities = lappend(cpstate->entities, entity); - entities = lappend(entities, entity); - - /* transform the properties if they exist */ - if (node->props) + if ((path->var_name == NULL || list_length(path->path) == 3) && + i > 0 && i == list_length(path->path) - 1 && + node->name == NULL && node->label == NULL && + node->props == NULL) { - Node *n = NULL; - Node *prop_var = NULL; - Node *prop_expr = NULL; - - /* - * We need to build a transformed properties(prop_var) - * expression IF the properties variable already exists from a - * previous clause. Please note that the "found" prop_var was - * previously transformed. - */ + cypher_relationship *prev_rel = + (cypher_relationship *)list_nth(path->path, i - 1); - /* get the prop_var if it was previously resolved */ - if (node->name != NULL) + if (prev_rel->varlen != NULL) { - prop_var = colNameToVar(pstate, node->name, false, - node->location); - } + FuncCall *vle_func = (FuncCall *)prev_rel->varlen; + Node *start_arg = linitial(vle_func->args); - /* - * If prop_var exists and is an alias, just pass it through by - * assigning the prop_expr the prop_var. - */ - if (prop_var != NULL && - pg_strncasecmp(node->name, AGE_DEFAULT_ALIAS_PREFIX, - sizeof(AGE_DEFAULT_ALIAS_PREFIX) - 1) == 0) - { - prop_expr = prop_var; - } - /* - * Else, if it exists and is not an alias, create the prop_expr - * as a transformed properties(prop_var) function node. - */ - else if (prop_var != NULL) - { - prop_expr = make_age_properties_func_expr(prop_var); + if (!IsA(start_arg, A_Const)) + { + output_node = false; + } } + } - if (is_ag_node(node->props, cypher_map)) + /* + * Symmetric case for right-bound VLE: the anonymous start vertex is + * not bound, filtered, or returned. Let age_vle() iterate starts + * internally in paths-to mode. + */ + if (i == 0 && + node->parsed_name == NULL && node->label == NULL && + node->props == NULL && + list_length(path->path) == 3) + { + cypher_relationship *next_rel = + (cypher_relationship *)lfirst(lnext(path->path, lc)); + cypher_node *next_node = + (cypher_node *)lfirst(lnext(path->path, + lnext(path->path, lc))); + + if (next_rel->varlen != NULL) { - ((cypher_map*)node->props)->keep_null = true; - } - n = create_property_constraints(cpstate, entity, node->props, - prop_expr); + FuncCall *vle_func = (FuncCall *)next_rel->varlen; + ListCell *start_arg_cell = list_head(vle_func->args); + ListCell *end_arg_cell = lnext(vle_func->args, + start_arg_cell); + Node *start_arg = lfirst(start_arg_cell); + Node *end_arg = lfirst(end_arg_cell); + + if (next_node->parsed_name == NULL && + (next_node->label != NULL || next_node->props != NULL) && + next_node->name != NULL && + !IsA(start_arg, A_Const) && IsA(end_arg, A_Const)) + { + A_Const *null_const = makeNode(A_Const); + ColumnRef *end_ref = makeNode(ColumnRef); + + null_const->isnull = true; + null_const->location = -1; + lfirst(start_arg_cell) = null_const; + + end_ref->fields = list_make2(makeString(next_node->name), + makeString("id")); + end_ref->location = next_node->location; + lfirst(end_arg_cell) = end_ref; - cpstate->property_constraint_quals = - lappend(cpstate->property_constraint_quals, n); + output_node = false; + } + } } + entity = transform_match_node_entity(cpstate, query, node, + output_node, valid_label); + entities = lappend(entities, entity); + prev_entity = entity; } /* odd increments of i are edges */ @@ -5298,6 +5395,7 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, else { transform_entity *vle_entity = NULL; + ListCell *next_lc = lnext(path->path, lc); /* * Check to see if the previous node was originally created @@ -5318,12 +5416,60 @@ static List *transform_match_entities(cypher_parsestate *cpstate, Query *query, cr->fields = list_make1(linitial(cr->fields)); } + if (list_length(path->path) == 3 && + prev_entity != NULL && + next_lc != NULL) + { + cypher_node *next_node = lfirst(next_lc); + FuncCall *func = (FuncCall *)rel->varlen; + ListCell *start_arg_cell = list_head(func->args); + ListCell *end_arg_cell = lnext(func->args, + start_arg_cell); + Node *start_arg = linitial(func->args); + Node *end_arg = lfirst(end_arg_cell); + bool needs_pretransform = false; + + if (next_node->name != NULL && + next_node->parsed_name == NULL && + IsA(end_arg, A_Const) && + !IsA(start_arg, A_Const)) + { + ColumnRef *end_ref = makeNode(ColumnRef); + + end_ref->fields = list_make2(makeString(next_node->name), + makeString("id")); + end_ref->location = next_node->location; + lfirst(end_arg_cell) = end_ref; + needs_pretransform = true; + } + else if (next_node->name != NULL && + next_node->parsed_name == NULL && + !prev_entity->in_join_tree && + !IsA(end_arg, A_Const)) + { + needs_pretransform = true; + } + + if (needs_pretransform) + { + pretransformed_node = + transform_match_node_entity(cpstate, query, + next_node, true, + valid_label); + skip_pretransformed_node = true; + } + } + /* make a transform entity for the vle */ vle_entity = transform_VLE_edge_entity(cpstate, rel, query); /* add the entity in */ cpstate->entities = lappend(cpstate->entities, vle_entity); entities = lappend(entities, vle_entity); + if (pretransformed_node != NULL) + { + entities = lappend(entities, pretransformed_node); + } prev_entity = entity; } diff --git a/src/backend/parser/cypher_expr.c b/src/backend/parser/cypher_expr.c index a7ffd79e7..902970bda 100644 --- a/src/backend/parser/cypher_expr.c +++ b/src/backend/parser/cypher_expr.c @@ -99,6 +99,46 @@ static Oid agtype_string_match_starts_with_oid = InvalidOid; static Oid agtype_string_match_ends_with_oid = InvalidOid; static Oid agtype_string_match_contains_oid = InvalidOid; static Oid text_to_agtype_oid = InvalidOid; +static Oid age_vle_path_length_oid = InvalidOid; +static Oid age_vle_path_node_count_oid = InvalidOid; +static Oid age_vle_edge_tail_count_oid = InvalidOid; +static Oid age_vle_list_is_empty_oid = InvalidOid; +static Oid age_vle_list_slice_count_oid = InvalidOid; +static Oid age_vle_list_slice_is_empty_oid = InvalidOid; +static Oid age_materialize_vle_list_slice_oid = InvalidOid; +static Oid age_materialize_vle_slice_boundary_oid = InvalidOid; +static Oid age_materialize_vle_edges_oid = InvalidOid; +static Oid age_materialize_vle_nodes_oid = InvalidOid; +static Oid age_materialize_vle_node_at_oid = InvalidOid; +static Oid age_materialize_vle_node_tail_last_oid = InvalidOid; +static Oid age_vle_node_tail_last_id_oid = InvalidOid; +static Oid age_vle_node_id_at_oid = InvalidOid; +static Oid age_vle_node_label_at_oid = InvalidOid; +static Oid age_vle_node_labels_at_oid = InvalidOid; +static Oid age_vle_node_properties_at_oid = InvalidOid; +static Oid age_materialize_vle_nodes_slice_oid = InvalidOid; +static Oid age_materialize_vle_nodes_tail_oid = InvalidOid; +static Oid age_materialize_vle_nodes_reversed_oid = InvalidOid; +static Oid age_materialize_vle_edge_at_oid = InvalidOid; +static Oid age_materialize_vle_edge_reversed_at_oid = InvalidOid; +static Oid age_materialize_vle_edge_tail_last_oid = InvalidOid; +static Oid age_vle_edge_tail_last_id_oid = InvalidOid; +static Oid age_vle_tail_last_field_oid = InvalidOid; +static Oid age_vle_tail_last_edge_endpoint_oid = InvalidOid; +static Oid age_vle_tail_last_endpoint_field_oid = InvalidOid; +static Oid age_vle_edge_id_at_oid = InvalidOid; +static Oid age_vle_edge_index_exists_oid = InvalidOid; +static Oid age_vle_edge_indices_equal_oid = InvalidOid; +static Oid age_vle_edge_reversed_index_equal_oid = InvalidOid; +static Oid age_vle_edge_label_at_oid = InvalidOid; +static Oid age_vle_edge_properties_at_oid = InvalidOid; +static Oid age_vle_edge_start_node_at_oid = InvalidOid; +static Oid age_vle_edge_end_node_at_oid = InvalidOid; +static Oid age_vle_edge_endpoint_field_at_oid = InvalidOid; +static Oid age_vle_edge_start_id_at_oid = InvalidOid; +static Oid age_vle_edge_end_id_at_oid = InvalidOid; +static Oid age_materialize_vle_edges_tail_oid = InvalidOid; +static Oid age_materialize_vle_edges_reversed_oid = InvalidOid; static Node *transform_cypher_expr_recurse(cypher_parsestate *cpstate, Node *expr); @@ -116,7 +156,20 @@ static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, cypher_bool_const *bc); static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, cypher_integer_const *ic); +static Const *make_agtype_integer_const(int64 value, int location); static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_self_membership( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_index_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_boundary_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_reversed_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_nested_transform_equality( + cypher_parsestate *cpstate, A_Expr *a); +static Node *try_transform_vle_edge_normalized_equality( + cypher_parsestate *cpstate, A_Expr *a); static Node *transform_cypher_param(cypher_parsestate *cpstate, cypher_param *cp); static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm); @@ -143,6 +196,133 @@ static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, FuncCall *fn, List *targs, Form_pg_proc procform, const char *extension); +static Expr *get_current_single_vle_path_expr(cypher_parsestate *cpstate, + Node *arg); +static Expr *get_current_vle_relationship_list_expr( + cypher_parsestate *cpstate, Node *arg); +static Node *try_transform_vle_path_length(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_id_access(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_access(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_endpoint_field(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_tail_last_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Expr *get_current_vle_edge_expr(cypher_parsestate *cpstate, Node *arg); +static Node *try_transform_vle_edge_variable_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_node_field(cypher_parsestate *cpstate, + FuncCall *fn); +static bool parse_vle_path_boundary_list_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name, + int64 *index); +static bool parse_vle_normal_or_boundary_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr); +static bool parse_vle_nested_transform_edge_equality_index( + cypher_parsestate *cpstate, Node *node, Expr **vle_expr, + Node **index_expr, bool *reversed_index); +static bool parse_vle_edge_endpoint_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr, + bool *start_endpoint); +static bool parse_vle_path_indexed_list_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Node **index_expr); +static Node *try_transform_vle_path_boundary_field(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_field( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_edge_label(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_edge_properties( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_slice_size(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_is_empty(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn, + int64 mode_offset); +static Node *try_transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_slice_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_nested_transform_index_id_function( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_size(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_is_empty(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_head_last(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn, int64 mode_offset); +static Node *try_transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_list_tail_reverse( + cypher_parsestate *cpstate, FuncCall *fn); +static Node *try_transform_vle_path_relationships(cypher_parsestate *cpstate, + FuncCall *fn); +static Node *try_transform_vle_path_nodes(cypher_parsestate *cpstate, + FuncCall *fn); +static bool get_nonnegative_integer_const(Node *node, int64 *value); +static bool parse_vle_path_nested_transform_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Const **lower_expr, + Const **upper_expr, + int64 *mode); +static Node *try_transform_vle_path_tail_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_nested_transform_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_nodes_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_nodes_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_list_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_boundary_id_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_boundary_property_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_path_relationships_access( + cypher_parsestate *cpstate, A_Indirection *a_ind); +static Node *try_transform_vle_edge_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind); +static Node *try_transform_vle_path_relationships_slice( + cypher_parsestate *cpstate, A_Indirection *a_ind); static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, Form_pg_proc procform, List *fargs, @@ -174,6 +354,43 @@ static Oid get_agtype_string_match_starts_with_oid(void); static Oid get_agtype_string_match_ends_with_oid(void); static Oid get_agtype_string_match_contains_oid(void); static Oid get_text_to_agtype_oid(void); +static Oid get_age_vle_path_length_oid(void); +static Oid get_age_vle_path_node_count_oid(void); +static Oid get_age_vle_edge_tail_count_oid(void); +static Oid get_age_vle_list_is_empty_oid(void); +static Oid get_age_vle_list_slice_count_oid(void); +static Oid get_age_vle_list_slice_is_empty_oid(void); +static Oid get_age_materialize_vle_list_slice_oid(void); +static Oid get_age_materialize_vle_slice_boundary_oid(void); +static Oid get_age_materialize_vle_edges_oid(void); +static Oid get_age_materialize_vle_nodes_oid(void); +static Oid get_age_materialize_vle_node_at_oid(void); +static Oid get_age_materialize_vle_node_tail_last_oid(void); +static Oid get_age_vle_node_tail_last_id_oid(void); +static Oid get_age_vle_node_id_at_oid(void); +static Oid get_age_materialize_vle_nodes_slice_oid(void); +static Oid get_age_materialize_vle_nodes_tail_oid(void); +static Oid get_age_materialize_vle_nodes_reversed_oid(void); +static Oid get_age_materialize_vle_edge_at_oid(void); +static Oid get_age_materialize_vle_edge_reversed_at_oid(void); +static Oid get_age_materialize_vle_edge_tail_last_oid(void); +static Oid get_age_vle_edge_tail_last_id_oid(void); +static Oid get_age_vle_tail_last_field_oid(void); +static Oid get_age_vle_tail_last_edge_endpoint_oid(void); +static Oid get_age_vle_tail_last_endpoint_field_oid(void); +static Oid get_age_vle_edge_id_at_oid(void); +static Oid get_age_vle_edge_index_exists_oid(void); +static Oid get_age_vle_edge_indices_equal_oid(void); +static Oid get_age_vle_edge_reversed_index_equal_oid(void); +static Oid get_age_vle_edge_label_at_oid(void); +static Oid get_age_vle_edge_properties_at_oid(void); +static Oid get_age_vle_edge_start_node_at_oid(void); +static Oid get_age_vle_edge_end_node_at_oid(void); +static Oid get_age_vle_edge_endpoint_field_at_oid(void); +static Oid get_age_vle_edge_start_id_at_oid(void); +static Oid get_age_vle_edge_end_id_at_oid(void); +static Oid get_age_materialize_vle_edges_tail_oid(void); +static Oid get_age_materialize_vle_edges_reversed_oid(void); static bool function_exists(char *funcname, char *extension); static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, Oid source_oid, Oid target_oid, @@ -597,8 +814,38 @@ static Node *transform_AEXPR_OP(cypher_parsestate *cpstate, A_Expr *a) { ParseState *pstate = (ParseState *)cpstate; Node *last_srf = pstate->p_last_srf; - Node *lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); - Node *rexpr = transform_cypher_expr_recurse(cpstate, a->rexpr); + Node *fast_expr = NULL; + Node *lexpr = NULL; + Node *rexpr = NULL; + + fast_expr = try_transform_vle_edge_index_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_boundary_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_reversed_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_nested_transform_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_normalized_equality(cpstate, a); + if (fast_expr != NULL) + { + return fast_expr; + } + + lexpr = transform_cypher_expr_recurse(cpstate, a->lexpr); + rexpr = transform_cypher_expr_recurse(cpstate, a->rexpr); return (Node *)make_op(pstate, a->name, lexpr, rexpr, last_srf, a->location); @@ -646,6 +893,12 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) FuncExpr *func_in_expr; List *args = NIL; + result = try_transform_vle_edge_self_membership(cpstate, a); + if (result != NULL) + { + return result; + } + args = lappend(args, transform_cypher_expr_recurse(cpstate, a->rexpr)); args = lappend(args, transform_cypher_expr_recurse(cpstate, a->lexpr)); @@ -787,201 +1040,818 @@ static Node *transform_AEXPR_IN(cypher_parsestate *cpstate, A_Expr *a) return result; } -static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr) +static Node *try_transform_vle_edge_self_membership( + cypher_parsestate *cpstate, A_Expr *a) { - ParseState *pstate = (ParseState *)cpstate; - List *args = NIL; - const char *opname; - ListCell *la; + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool reversed_index = false; - switch (expr->boolop) + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) { - case AND_EXPR: - opname = "AND"; - break; - case OR_EXPR: - opname = "OR"; - break; - case NOT_EXPR: - opname = "NOT"; - break; - default: - ereport(ERROR, (errmsg_internal("unrecognized boolop: %d", - (int)expr->boolop))); return NULL; } - foreach (la, expr->args) + right_vle_expr = get_current_vle_relationship_list_expr(cpstate, + a->rexpr); + if (right_vle_expr == NULL) { - Node *arg = lfirst(la); - - arg = transform_cypher_expr_recurse(cpstate, arg); - arg = coerce_to_boolean(pstate, arg, opname); - - args = lappend(args, arg); + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &left_vle_expr, &index_expr) && + !parse_vle_nested_transform_edge_equality_index( + cpstate, a->lexpr, &left_vle_expr, &index_expr, &reversed_index)) + { + return NULL; + } + if (left_vle_expr != right_vle_expr) + { + return NULL; } - return (Node *)makeBoolExpr(expr->boolop, args, expr->location); + func_oid = get_age_vle_edge_index_exists_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(left_vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * function for transforming cypher_comparison_boolexpr. Since this node is a - * wrapper to let us know when a comparison occurs in a chained comparison, - * we convert it to a PG BoolExpr and transform it. - */ -static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, - cypher_comparison_boolexpr *b) +static Node *try_transform_vle_edge_index_equality( + cypher_parsestate *cpstate, A_Expr *a) { - BoolExpr *n = makeNode(BoolExpr); + A_Indirection *left_ind = NULL; + A_Indirection *right_ind = NULL; + A_Indices *left_indices = NULL; + A_Indices *right_indices = NULL; + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *left_index_expr = NULL; + Node *right_index_expr = NULL; + Oid func_oid; - n->boolop = b->boolop; - n->args = b->args; - n->location = b->location; + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0 || + !IsA(a->lexpr, A_Indirection) || + !IsA(a->rexpr, A_Indirection)) + { + return NULL; + } - return transform_BoolExpr(cpstate, n); -} + left_ind = (A_Indirection *)a->lexpr; + right_ind = (A_Indirection *)a->rexpr; + if (list_length(left_ind->indirection) != 1 || + list_length(right_ind->indirection) != 1 || + !IsA(linitial(left_ind->indirection), A_Indices) || + !IsA(linitial(right_ind->indirection), A_Indices)) + { + return NULL; + } + + left_indices = linitial(left_ind->indirection); + right_indices = linitial(right_ind->indirection); + if (left_indices->is_slice || left_indices->uidx == NULL || + right_indices->is_slice || right_indices->uidx == NULL) + { + return NULL; + } + left_vle_expr = get_current_vle_edge_expr(cpstate, left_ind->arg); + right_vle_expr = get_current_vle_edge_expr(cpstate, right_ind->arg); + if (left_vle_expr == NULL || right_vle_expr == NULL || + left_vle_expr != right_vle_expr) + { + return NULL; + } -static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, - cypher_bool_const *bc) + left_index_expr = transform_cypher_expr_recurse(cpstate, + left_indices->uidx); + right_index_expr = transform_cypher_expr_recurse(cpstate, + right_indices->uidx); + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(left_vle_expr, left_index_expr, + right_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_boundary_equality( + cypher_parsestate *cpstate, A_Expr *a) { - ParseState *pstate = (ParseState *)cpstate; - ParseCallbackState pcbstate; - Datum agt; - Const *c; + Node *boundary_node = NULL; + Node *indexed_node = NULL; + FuncCall *boundary_fn = NULL; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + Expr *boundary_vle_expr = NULL; + Expr *indexed_vle_expr = NULL; + Node *boundary_index_expr = NULL; + Node *indexed_index_expr = NULL; + char *boundary_name = NULL; + Oid func_oid; - setup_parser_errposition_callback(&pcbstate, pstate, bc->location); - agt = boolean_to_agtype(bc->boolean); - cancel_parser_errposition_callback(&pcbstate); + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } - /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ - c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = bc->location; + if (IsA(a->lexpr, FuncCall) && IsA(a->rexpr, A_Indirection)) + { + boundary_node = a->lexpr; + indexed_node = a->rexpr; + } + else if (IsA(a->lexpr, A_Indirection) && IsA(a->rexpr, FuncCall)) + { + boundary_node = a->rexpr; + indexed_node = a->lexpr; + } + else + { + return NULL; + } - return (Node *)c; -} + boundary_fn = (FuncCall *)boundary_node; + if (list_length(boundary_fn->funcname) != 1 || + list_length(boundary_fn->args) != 1) + { + return NULL; + } -static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, - cypher_integer_const *ic) -{ - ParseState *pstate = (ParseState *)cpstate; - ParseCallbackState pcbstate; - Datum agt; - Const *c; + boundary_name = strVal(linitial(boundary_fn->funcname)); + if (pg_strcasecmp(boundary_name, "head") == 0) + { + boundary_index_expr = (Node *)make_agtype_integer_const( + 0, boundary_fn->location); + } + else if (pg_strcasecmp(boundary_name, "last") == 0) + { + boundary_index_expr = (Node *)make_agtype_integer_const( + -1, boundary_fn->location); + } + else + { + return NULL; + } - setup_parser_errposition_callback(&pcbstate, pstate, ic->location); - agt = integer_to_agtype(ic->integer); - cancel_parser_errposition_callback(&pcbstate); + boundary_vle_expr = get_current_vle_edge_expr(cpstate, + linitial(boundary_fn->args)); + if (boundary_vle_expr == NULL) + { + return NULL; + } - /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ - c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); - c->location = ic->location; + a_ind = (A_Indirection *)indexed_node; + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - return (Node *)c; + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + + indexed_vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (indexed_vle_expr == NULL || indexed_vle_expr != boundary_vle_expr) + { + return NULL; + } + + indexed_index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(boundary_vle_expr, + boundary_index_expr, + indexed_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_cypher_param(cypher_parsestate *cpstate, - cypher_param *cp) +static bool parse_vle_reverse_index(cypher_parsestate *cpstate, Node *node, + Expr **vle_expr, Node **index_expr) { - ParseState *pstate = (ParseState *)cpstate; - Const *const_str; - FuncExpr *func_expr; - Oid func_access_oid; - List *args = NIL; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *reverse_fn = NULL; - if (!cpstate->params) + Assert(vle_expr != NULL); + Assert(index_expr != NULL); + + if (!IsA(node, A_Indirection)) { - ereport( - ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), - errmsg( - "parameters argument is missing from cypher() function call"), - parser_errposition(pstate, cp->location))); + return false; } - /* get the agtype_access_operator function */ - func_access_oid = get_agtype_access_operator_oid(); + a_ind = (A_Indirection *)node; + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } - args = lappend(args, copyObject(cpstate->params)); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return false; + } - const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, - string_to_agtype(cp->name), false, false); + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) + { + return false; + } - args = lappend(args, const_str); + *vle_expr = get_current_vle_edge_expr(cpstate, linitial(reverse_fn->args)); + if (*vle_expr == NULL) + { + return false; + } - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = cp->location; + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); - return (Node *)func_expr; + return true; } -static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, - cypher_map_projection *cmp) +static bool parse_vle_normal_or_boundary_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr) { - ParseState *pstate; - ListCell *lc; - List *keyvals; - Oid foid_agtype_build_map; - FuncExpr *fexpr_new_map; - bool has_all_prop_selector; - Node *transformed_map_var; - Oid foid_age_properties; - FuncExpr *fexpr_orig_map; + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *boundary_fn = NULL; + char *list_name = NULL; + char *boundary_name = NULL; - pstate = (ParseState *)cpstate; - keyvals = NIL; - has_all_prop_selector = false; - fexpr_new_map = NULL; + Assert(vle_expr != NULL); + Assert(index_expr != NULL); - /* - * Builds the original map: `age_properties(cmp->map_var)`. Whether map_var - * is compatible (map, vertex or edge) is checked during the execution of - * age_properties(). - */ - transformed_map_var = transform_cypher_expr_recurse(cpstate, - (Node *)cmp->map_var); - foid_age_properties = get_age_properties_oid(); - fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID, - list_make1(transformed_map_var), InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr_orig_map->location = cmp->location; + if (IsA(node, FuncCall)) + { + FuncCall *boundary_fn = (FuncCall *)node; + char *boundary_name = NULL; - /* - * Builds a new map. Each map projection element is transformed into a key - * value pair (except for the ALL_PROPERTIES_SELECTOR type). - */ - foreach (lc, cmp->map_elements) + if (list_length(boundary_fn->funcname) == 1 && + list_length(boundary_fn->args) == 1) + { + boundary_name = strVal(linitial(boundary_fn->funcname)); + if ((pg_strcasecmp(boundary_name, "head") == 0 || + pg_strcasecmp(boundary_name, "last") == 0) && + is_ag_node(linitial(boundary_fn->args), cypher_list)) + { + cypher_list *cl = (cypher_list *)linitial(boundary_fn->args); + + if (list_length(cl->elems) != 1) + { + return false; + } + + return parse_vle_normal_or_boundary_index( + cpstate, linitial(cl->elems), vle_expr, index_expr); + } + } + } + + if (IsA(node, A_Indirection)) { - cypher_map_projection_element *elem; - Const *key; - Node *val; + a_ind = (A_Indirection *)node; - elem = lfirst(lc); - key = NULL; - val = NULL; + if (parse_vle_path_indexed_list_index(cpstate, a_ind, vle_expr, + &list_name, index_expr)) + { + return pg_strcasecmp(list_name, "relationships") == 0; + } - if (elem->type == ALL_PROPERTIES_SELECTOR) + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - has_all_prop_selector = true; - continue; + return false; } - /* Makes key and val based on elem->type */ - switch (elem->type) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) { - case PROPERTY_SELECTOR: - { - Oid foid_access_op; - FuncExpr *fexpr_access_op; - ArrayExpr *args_access_op; - Const *key_agtype; + return false; + } - /* Makes key from elem->key */ - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(elem->key), false, false); + *vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (*vle_expr == NULL) + { + return false; + } - /* Makes val from `age_properties(cmp->map_var).key` */ + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + return true; + } + + if (!IsA(node, FuncCall)) + { + return false; + } + + boundary_fn = (FuncCall *)node; + if (list_length(boundary_fn->funcname) != 1 || + list_length(boundary_fn->args) != 1) + { + return false; + } + + boundary_name = strVal(linitial(boundary_fn->funcname)); + if (pg_strcasecmp(boundary_name, "head") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + 0, boundary_fn->location); + } + else if (pg_strcasecmp(boundary_name, "last") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + -1, boundary_fn->location); + } + else + { + return false; + } + + *vle_expr = get_current_vle_edge_expr(cpstate, linitial(boundary_fn->args)); + + return *vle_expr != NULL; +} + +static Node *try_transform_vle_edge_normalized_equality( + cypher_parsestate *cpstate, A_Expr *a) +{ + Expr *left_vle_expr = NULL; + Expr *right_vle_expr = NULL; + Node *left_index_expr = NULL; + Node *right_index_expr = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } + + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &left_vle_expr, + &left_index_expr) || + !parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &right_vle_expr, + &right_index_expr)) + { + return NULL; + } + if (left_vle_expr == NULL || left_vle_expr != right_vle_expr) + { + return NULL; + } + + func_oid = get_age_vle_edge_indices_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(left_vle_expr, left_index_expr, + right_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_nested_transform_edge_equality_index( + cypher_parsestate *cpstate, Node *node, Expr **vle_expr, + Node **index_expr, bool *reversed_index) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + char *list_name = NULL; + int64 index; + int64 mode; + + Assert(vle_expr != NULL); + Assert(index_expr != NULL); + Assert(reversed_index != NULL); + + if (!IsA(node, A_Indirection)) + { + return false; + } + + a_ind = (A_Indirection *)node; + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &index)) + { + return false; + } + + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return false; + } + + if (mode >= 360) + { + *reversed_index = false; + index += 2; + } + else if (mode >= 240) + { + *reversed_index = true; + index += 1; + } + else if (mode >= 120) + { + *reversed_index = true; + } + else + { + return false; + } + + *index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(indices->uidx)); + + return true; +} + +static Node *try_transform_vle_edge_reversed_equality( + cypher_parsestate *cpstate, A_Expr *a) +{ + Expr *reversed_vle_expr = NULL; + Expr *normal_vle_expr = NULL; + Node *reversed_index_expr = NULL; + Node *normal_index_expr = NULL; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } + + if (!parse_vle_reverse_index(cpstate, a->lexpr, &reversed_vle_expr, + &reversed_index_expr)) + { + if (!parse_vle_reverse_index(cpstate, a->rexpr, &reversed_vle_expr, + &reversed_index_expr)) + { + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &normal_vle_expr, + &normal_index_expr)) + { + return NULL; + } + } + else if (!parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &normal_vle_expr, + &normal_index_expr)) + { + return NULL; + } + + if (normal_vle_expr == NULL || normal_vle_expr != reversed_vle_expr) + { + return NULL; + } + + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(reversed_vle_expr, + reversed_index_expr, + normal_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_nested_transform_equality( + cypher_parsestate *cpstate, A_Expr *a) +{ + Expr *nested_vle_expr = NULL; + Expr *normal_vle_expr = NULL; + Expr *other_nested_vle_expr = NULL; + Node *nested_index_expr = NULL; + Node *normal_index_expr = NULL; + Node *other_nested_index_expr = NULL; + bool nested_reversed = false; + bool other_nested_reversed = false; + Oid func_oid; + + if (list_length(a->name) != 1 || + pg_strcasecmp(strVal(linitial(a->name)), "=") != 0) + { + return NULL; + } + + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->lexpr, &nested_vle_expr, &nested_index_expr, + &nested_reversed)) + { + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->rexpr, &nested_vle_expr, &nested_index_expr, + &nested_reversed)) + { + return NULL; + } + if (!parse_vle_normal_or_boundary_index(cpstate, a->lexpr, + &normal_vle_expr, + &normal_index_expr)) + { + return NULL; + } + } + else if (!parse_vle_normal_or_boundary_index(cpstate, a->rexpr, + &normal_vle_expr, + &normal_index_expr)) + { + if (!parse_vle_nested_transform_edge_equality_index( + cpstate, a->rexpr, &other_nested_vle_expr, + &other_nested_index_expr, &other_nested_reversed) || + other_nested_vle_expr == NULL || + other_nested_vle_expr != nested_vle_expr) + { + return NULL; + } + + if (nested_reversed == other_nested_reversed) + { + func_oid = get_age_vle_edge_indices_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, nested_index_expr, + other_nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + if (nested_reversed) + { + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, nested_index_expr, + other_nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + return (Node *)makeFuncExpr( + func_oid, AGTYPEOID, + list_make3(nested_vle_expr, other_nested_index_expr, + nested_index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } + + if (normal_vle_expr == NULL || normal_vle_expr != nested_vle_expr) + { + return NULL; + } + + if (nested_reversed) + { + func_oid = get_age_vle_edge_reversed_index_equal_oid(); + } + else + { + func_oid = get_age_vle_edge_indices_equal_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(nested_vle_expr, + nested_index_expr, + normal_index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *transform_BoolExpr(cypher_parsestate *cpstate, BoolExpr *expr) +{ + ParseState *pstate = (ParseState *)cpstate; + List *args = NIL; + const char *opname; + ListCell *la; + + switch (expr->boolop) + { + case AND_EXPR: + opname = "AND"; + break; + case OR_EXPR: + opname = "OR"; + break; + case NOT_EXPR: + opname = "NOT"; + break; + default: + ereport(ERROR, (errmsg_internal("unrecognized boolop: %d", + (int)expr->boolop))); + return NULL; + } + + foreach (la, expr->args) + { + Node *arg = lfirst(la); + + arg = transform_cypher_expr_recurse(cpstate, arg); + arg = coerce_to_boolean(pstate, arg, opname); + + args = lappend(args, arg); + } + + return (Node *)makeBoolExpr(expr->boolop, args, expr->location); +} + +/* + * function for transforming cypher_comparison_boolexpr. Since this node is a + * wrapper to let us know when a comparison occurs in a chained comparison, + * we convert it to a PG BoolExpr and transform it. + */ +static Node *transform_cypher_comparison_boolexpr(cypher_parsestate *cpstate, + cypher_comparison_boolexpr *b) +{ + BoolExpr *n = makeNode(BoolExpr); + + n->boolop = b->boolop; + n->args = b->args; + n->location = b->location; + + return transform_BoolExpr(cpstate, n); +} + + +static Node *transform_cypher_bool_const(cypher_parsestate *cpstate, + cypher_bool_const *bc) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseCallbackState pcbstate; + Datum agt; + Const *c; + + setup_parser_errposition_callback(&pcbstate, pstate, bc->location); + agt = boolean_to_agtype(bc->boolean); + cancel_parser_errposition_callback(&pcbstate); + + /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = bc->location; + + return (Node *)c; +} + +static Node *transform_cypher_integer_const(cypher_parsestate *cpstate, + cypher_integer_const *ic) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseCallbackState pcbstate; + Datum agt; + Const *c; + + setup_parser_errposition_callback(&pcbstate, pstate, ic->location); + agt = integer_to_agtype(ic->integer); + cancel_parser_errposition_callback(&pcbstate); + + /* typtypmod, typcollation, typlen, and typbyval of agtype are hard-coded. */ + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = ic->location; + + return (Node *)c; +} + +static Const *make_agtype_integer_const(int64 value, int location) +{ + Datum agt; + Const *c; + + agt = integer_to_agtype(value); + c = makeConst(AGTYPEOID, -1, InvalidOid, -1, agt, false, false); + c->location = location; + + return c; +} + +static Node *transform_cypher_param(cypher_parsestate *cpstate, + cypher_param *cp) +{ + ParseState *pstate = (ParseState *)cpstate; + Const *const_str; + FuncExpr *func_expr; + Oid func_access_oid; + List *args = NIL; + + if (!cpstate->params) + { + ereport( + ERROR, + (errcode(ERRCODE_UNDEFINED_PARAMETER), + errmsg( + "parameters argument is missing from cypher() function call"), + parser_errposition(pstate, cp->location))); + } + + /* get the agtype_access_operator function */ + func_access_oid = get_agtype_access_operator_oid(); + + 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); + func_expr->location = cp->location; + + return (Node *)func_expr; +} + +static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, + cypher_map_projection *cmp) +{ + ParseState *pstate; + ListCell *lc; + List *keyvals; + Oid foid_agtype_build_map; + FuncExpr *fexpr_new_map; + bool has_all_prop_selector; + Node *transformed_map_var; + Oid foid_age_properties; + FuncExpr *fexpr_orig_map; + + pstate = (ParseState *)cpstate; + keyvals = NIL; + has_all_prop_selector = false; + fexpr_new_map = NULL; + + /* + * Builds the original map: `age_properties(cmp->map_var)`. Whether map_var + * is compatible (map, vertex or edge) is checked during the execution of + * age_properties(). + */ + transformed_map_var = transform_cypher_expr_recurse(cpstate, + (Node *)cmp->map_var); + foid_age_properties = get_age_properties_oid(); + fexpr_orig_map = makeFuncExpr(foid_age_properties, AGTYPEOID, + list_make1(transformed_map_var), InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr_orig_map->location = cmp->location; + + /* + * Builds a new map. Each map projection element is transformed into a key + * value pair (except for the ALL_PROPERTIES_SELECTOR type). + */ + foreach (lc, cmp->map_elements) + { + cypher_map_projection_element *elem; + Const *key; + Node *val; + + elem = lfirst(lc); + key = NULL; + val = NULL; + + if (elem->type == ALL_PROPERTIES_SELECTOR) + { + has_all_prop_selector = true; + continue; + } + + /* Makes key and val based on elem->type */ + switch (elem->type) + { + case PROPERTY_SELECTOR: + { + Oid foid_access_op; + FuncExpr *fexpr_access_op; + ArrayExpr *args_access_op; + Const *key_agtype; + + /* Makes key from elem->key */ + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(elem->key), false, false); + + /* Makes val from `age_properties(cmp->map_var).key` */ key_agtype = makeConst(AGTYPEOID, -1, InvalidOid, -1, string_to_agtype(elem->key), false, false); @@ -996,1459 +1866,6550 @@ static Node *transform_cypher_map_projection(cypher_parsestate *cpstate, fexpr_access_op->location = -1; val = (Node *)fexpr_access_op; - break; + break; + } + case LITERAL_ENTRY: + { + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(elem->key), false, false); + val = transform_cypher_expr_recurse(cpstate, elem->value); + break; + } + case VARIABLE_SELECTOR: + { + char *key_str; + List *fields; + + Assert(IsA(elem->value, ColumnRef)); + + /* Makes key from the ColumnRef's field */ + fields = ((ColumnRef *)elem->value)->fields; + key_str = strVal(lfirst(list_head(fields))); + key = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(key_str), false, false); + + val = transform_cypher_expr_recurse(cpstate, elem->value); + break; + } + case ALL_PROPERTIES_SELECTOR: + { + /* + * Key value pairs of the original map are added later outside + * the loop. Control never reaches this block. + */ + break; + } + default: + { + elog(ERROR, "unknown map projection element type"); + } + } + + Assert(key); + Assert(val); + keyvals = lappend(lappend(keyvals, key), val); + } + + if (keyvals) + { + foid_agtype_build_map = get_agtype_build_map_nonull_oid(); + fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr_new_map->location = cmp->location; + } + + /* + * In case .* is present, returns age_properties(cmp->map_var) + the new + * map. Else, returns the new map. + */ + if (has_all_prop_selector) + { + if (!keyvals) + { + return (Node *)fexpr_orig_map; + } + else + { + return (Node *)make_op(pstate, list_make1(makeString("+")), + (Node *)fexpr_orig_map, + (Node *)fexpr_new_map, + pstate->p_last_srf, -1); + } + } + else + { + Assert(!has_all_prop_selector && fexpr_new_map); + return (Node *)fexpr_new_map; + } +} + +/* + * Helper function to transform a cypher map into an agtype map. The function + * will use agtype_add to concatenate the argument list when the number of + * parameters (keys and values) exceeds 100, a PG limitation. + */ +static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) +{ + ParseState *pstate = (ParseState *)cpstate; + List *newkeyvals_args = NIL; + ListCell *le = NULL; + FuncExpr *fexpr = NULL; + FuncExpr *aa_lhs_arg = NULL; + Oid abm_func_oid = InvalidOid; + Oid aa_func_oid = InvalidOid; + int nkeyvals = 0; + int i = 0; + + /* get the number of keys and values */ + nkeyvals = list_length(cm->keyvals); + + /* error out if it isn't even */ + if (nkeyvals % 2 != 0) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("number of keys does not match number of values"))); + } + + if (nkeyvals == 0) + { + abm_func_oid = get_agtype_build_empty_map_oid(); + } + else if (!cm->keep_null) + { + abm_func_oid = get_agtype_build_map_nonull_oid(); + } + else + { + abm_func_oid = get_agtype_build_map_oid(); + } + + /* get the concat function oid, if necessary */ + if (nkeyvals > 100) + { + aa_func_oid = get_agtype_add_oid(); + } + + /* get the key/val list */ + le = list_head(cm->keyvals); + /* while we have key/val to process */ + while (le != NULL) + { + Node *key = NULL; + Node *val = NULL; + Node *newval = NULL; + ParseCallbackState pcbstate; + Const *newkey = NULL; + + /* get the key */ + key = lfirst(le); + le = lnext(cm->keyvals, le); + /* get the value */ + val = lfirst(le); + le = lnext(cm->keyvals, le); + + /* transform the value */ + newval = transform_cypher_expr_recurse(cpstate, val); + + /* + * If we have more than 50 key/value pairs, 100 elements, we will need + * to add in the list concatenation function. + */ + if (i >= 50) + { + /* build the object for the first 50 pairs for concat */ + fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* initial case, set up for concatenating 2 lists */ + if (aa_lhs_arg == NULL) + { + aa_lhs_arg = fexpr; + } + /* + * For every other case, concatenate the list on to the previous + * concatenate operation. + */ + else + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* set the lhs to the concatenation operation */ + aa_lhs_arg = fexpr; + } + + /* reset for the next 50 pairs */ + newkeyvals_args = NIL; + i = 0; + fexpr = NULL; + } + + /* build and append the transformed key/val pair */ + setup_parser_errposition_callback(&pcbstate, pstate, cm->location); + /* typtypmod, typcollation, typlen, and typbyval of agtype are */ + /* hard-coded. */ + newkey = makeConst(TEXTOID, -1, InvalidOid, -1, + CStringGetTextDatum(strVal(key)), false, false); + cancel_parser_errposition_callback(&pcbstate); + + newkeyvals_args = lappend(lappend(newkeyvals_args, newkey), newval); + + i++; + } + + /* now build the final map function */ + fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cm->location; + + /* + * If there was a previous concatenation, build a final concatenation + * function node. + */ + if (aa_lhs_arg != NULL) + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + } + + return (Node *)fexpr; +} + +/* + * Helper function to transform a cypher list into an agtype list. The function + * will use agtype_add to concatenate argument lists when the number of list + * elements, parameters, exceeds 100, a PG limitation. + */ +static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) +{ + List *abl_args = NIL; + ListCell *le = NULL; + FuncExpr *aa_lhs_arg = NULL; + FuncExpr *fexpr = NULL; + Oid abl_func_oid = InvalidOid; + Oid aa_func_oid = InvalidOid; + int nelems = 0; + int i = 0; + + /* determine which build function we need */ + nelems = list_length(cl->elems); + if (nelems == 0) + { + abl_func_oid = get_agtype_build_empty_list_oid(); + } + else + { + abl_func_oid = get_agtype_build_list_oid(); + } + + /* get the concat function oid, if necessary */ + if (nelems > 100) + { + aa_func_oid = get_agtype_add_oid(); + } + + /* iterate through the list of elements */ + foreach (le, cl->elems) + { + Node *texpr = NULL; + + /* transform the argument */ + texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); + + /* + * If we have more than 100 elements we will need to add in the list + * concatenation function. + */ + if (i >= 100) + { + /* build the list function node argument for concatenate */ + fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* initial case, set up for concatenating 2 lists */ + if (aa_lhs_arg == NULL) + { + aa_lhs_arg = fexpr; + } + /* + * For every other case, concatenate the list on to the previous + * concatenate operation. + */ + else + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* set the lhs to the concatenation operation */ + aa_lhs_arg = fexpr; + } + + /* reset */ + abl_args = NIL; + i = 0; + fexpr = NULL; + } + + /* now add the latest transformed expression to the list */ + abl_args = lappend(abl_args, texpr); + i++; + } + + /* now build the final list function */ + fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + fexpr->location = cl->location; + + /* + * If there was a previous concatenation or list function, build a final + * concatenation function node + */ + if (aa_lhs_arg != NULL) + { + List *aa_args = list_make2(aa_lhs_arg, fexpr); + + fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + } + + return (Node *)fexpr; +} + +/* makes a VARIADIC agtype array */ +static ArrayExpr *make_agtype_array_expr(List *args) +{ + ArrayExpr *newa = makeNode(ArrayExpr); + + newa->elements = args; + + /* assume all the variadic arguments were coerced to the same type */ + newa->element_typeid = AGTYPEOID; + newa->array_typeid = AGTYPEARRAYOID; + + if (!OidIsValid(newa->array_typeid)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("could not find array type for data type %s", + format_type_be(newa->element_typeid)))); + } + + /* array_collid will be set by parse_collate.c */ + newa->multidims = false; + + return newa; +} + +/* + * Transform a ColumnRef for indirection. Try to find the rte that the ColumnRef + * references and pass the properties of that rte as what the ColumnRef is + * referencing. Otherwise, reference the Var. + */ +static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, + ColumnRef *cr) +{ + ParseState *pstate = (ParseState *)cpstate; + ParseNamespaceItem *pnsi = NULL; + Node *field1 = linitial(cr->fields); + char *relname = NULL; + Node *node = NULL; + int levels_up = 0; + + Assert(IsA(field1, String)); + relname = strVal(field1); + + /* locate the referenced RTE (used to be find_rte(cpstate, relname)) */ + pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, + &levels_up); + + /* + * If we didn't find anything, try looking for a previous variable + * reference. Otherwise, return NULL (colNameToVar will return NULL + * if nothing is found). + */ + if (!pnsi) + { + Node *prev_var = colNameToVar(pstate, relname, false, cr->location); + + return prev_var; + } + + /* find the properties column of the NSI and return a var for it */ + node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", + cr->location); + + /* + * If there's no "properties" column, continue transforming the + * ColumnRef as an agtype value and try to apply the indirection via + * agtype_access_operator. + */ + return node; +} + +static Node *transform_A_Indirection(cypher_parsestate *cpstate, + A_Indirection *a_ind) +{ + ParseState *pstate = &cpstate->pstate; + int location; + ListCell *lc = NULL; + Node *ind_arg_expr = NULL; + FuncExpr *func_expr = NULL; + Oid func_access_oid = InvalidOid; + Oid func_slice_oid = InvalidOid; + List *args = NIL; + bool is_access = false; + Node *fast_expr = NULL; + + /* validate that we have an indirection with at least 1 entry */ + Assert(a_ind != NULL && list_length(a_ind->indirection)); + + fast_expr = try_transform_vle_path_tail_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_reverse_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nested_transform_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nodes_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_nodes_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_list_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_boundary_id_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_boundary_property_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_relationships_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_edge_reverse_access(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + fast_expr = try_transform_vle_path_relationships_slice(cpstate, a_ind); + if (fast_expr != NULL) + { + return fast_expr; + } + + /* get the agtype_access_operator function */ + func_access_oid = get_agtype_access_operator_oid(); + /* get the agtype_access_slice function */ + func_slice_oid = get_agtype_access_slice_oid(); + + /* + * If the indirection argument is a ColumnRef, we want to pull out the + * properties, as a var node, if possible. + */ + if (IsA(a_ind->arg, ColumnRef)) + { + ColumnRef *cr = (ColumnRef *)a_ind->arg; + + ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr); + } + + /* + * If we didn't get the properties from a ColumnRef, just transform the + * indirection argument. + */ + if (ind_arg_expr == NULL) + { + ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg); + } + + ind_arg_expr = coerce_to_common_type(pstate, ind_arg_expr, AGTYPEOID, + "A_indirection"); + + /* get the location of the expression */ + location = exprLocation(ind_arg_expr); + + /* add the expression as the first entry */ + args = lappend(args, ind_arg_expr); + + /* iterate through the indirections */ + foreach (lc, a_ind->indirection) + { + Node *node = lfirst(lc); + + /* is this a slice? */ + if (IsA(node, A_Indices) && ((A_Indices *)node)->is_slice) + { + A_Indices *indices = (A_Indices *)node; + + /* were we working on an access? if so, wrap and close it */ + if (is_access) + { + ArrayExpr *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 = location; + + /* + * The result of this function is the input to the next access + * or slice operator. So we need to start out with a new arg + * list with this function expression. + */ + args = lappend(NIL, func_expr); + + /* we are no longer working on an access */ + is_access = false; + + } + + /* add slice bounds to args */ + if (!indices->lidx) + { + A_Const *n = makeNode(A_Const); + n->isnull = true; + n->location = -1; + node = transform_cypher_expr_recurse(cpstate, (Node *)n); + } + else + { + node = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + args = lappend(args, node); + + if (!indices->uidx) + { + A_Const *n = makeNode(A_Const); + n->isnull = true; + n->location = -1; + node = transform_cypher_expr_recurse(cpstate, (Node *)n); + } + else + { + node = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + args = lappend(args, node); + + /* wrap and close it */ + func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args, + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + func_expr->location = location; + + /* + * The result of this function is the input to the next access + * or slice operator. So we need to start out with a new arg + * list with this function expression. + */ + args = lappend(NIL, func_expr); + } + /* is this a string or index?*/ + else if (IsA(node, String) || IsA(node, A_Indices)) + { + /* we are working on an access */ + is_access = true; + + /* is this an index? */ + if (IsA(node, A_Indices)) + { + A_Indices *indices = (A_Indices *)node; + + node = transform_cypher_expr_recurse(cpstate, indices->uidx); + args = lappend(args, node); + } + /* it must be a string */ + else + { + Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(strVal(node)), + false, false); + args = lappend(args, const_str); + } + } + /* not an indirection we understand */ + else + { + ereport(ERROR, + (errmsg("invalid indirection node %d", nodeTag(node)))); + } + } + + /* if we were doing an access, we need wrap the args with access func. */ + if (is_access) + { + ArrayExpr *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; + } + + Assert(func_expr != NULL); + func_expr->location = location; + + return (Node *)func_expr; +} + +static Node *transform_cypher_string_match(cypher_parsestate *cpstate, + cypher_string_match *csm_node) +{ + Node *expr; + FuncExpr *func_expr; + Oid func_access_oid; + List *args = NIL; + + switch (csm_node->operation) + { + case CSMO_STARTS_WITH: + func_access_oid = get_agtype_string_match_starts_with_oid(); + break; + case CSMO_ENDS_WITH: + func_access_oid = get_agtype_string_match_ends_with_oid(); + break; + case CSMO_CONTAINS: + func_access_oid = get_agtype_string_match_contains_oid(); + break; + + default: + ereport(ERROR, + (errmsg_internal("unknown Cypher string match operation"))); + } + + expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); + args = lappend(args, expr); + expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); + args = lappend(args, expr); + + func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, + InvalidOid, COERCE_EXPLICIT_CALL); + func_expr->location = csm_node->location; + + return (Node *)func_expr; +} + +/* + * Function to create a typecasting node + */ +static Node *transform_cypher_typecast(cypher_parsestate *cpstate, + cypher_typecast *ctypecast) +{ + List *fname; + FuncCall *fnode; + ParseState *pstate; + TypeName *target_typ; + + /* verify input parameter */ + Assert (cpstate != NULL); + Assert (ctypecast != NULL); + + /* create the qualified function name, schema first */ + fname = list_make1(makeString("ag_catalog")); + pstate = &cpstate->pstate; + target_typ = ctypecast->typname; + + if (list_length(target_typ->names) == 1) + { + char *typecast = strVal(linitial(target_typ->names)); + int typecast_len = strlen(typecast); + + /* append the name of the requested typecast function */ + if (typecast_len == 4 && pg_strcasecmp(typecast, "edge") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); + } + else if (typecast_len == 4 && pg_strcasecmp(typecast, "path") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); + } + else if (typecast_len == 6 && pg_strcasecmp(typecast, "vertex") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); + } + else if (typecast_len == 7 && pg_strcasecmp(typecast, "numeric") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); + } + else if (typecast_len == 5 && pg_strcasecmp(typecast, "float") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); + } + else if ((typecast_len == 3 && + pg_strcasecmp(typecast, "int") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "integer") == 0)) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); + } + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_float8") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); + } + else if (typecast_len == 9 && + pg_strcasecmp(typecast, "pg_bigint") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); + } + else if ((typecast_len == 4 && + pg_strcasecmp(typecast, "bool") == 0) || + (typecast_len == 7 && + pg_strcasecmp(typecast, "boolean") == 0)) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); + } + else if (typecast_len == 7 && + pg_strcasecmp(typecast, "pg_text") == 0) + { + fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_TEXT)); + } + else + { + goto fallback_coercion; + } + + /* make a function call node */ + fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, + ctypecast->location); + + /* return the transformed function */ + return transform_FuncCall(cpstate, fnode); + } + +fallback_coercion: + { + Oid source_oid; + Oid target_oid; + int32 t_typmod = -1; + Node *expr; + + /* transform the expr before casting */ + expr = transform_cypher_expr_recurse(cpstate, + ctypecast->expr); + + typenameTypeIdAndMod(pstate, target_typ, &target_oid, &t_typmod); + source_oid = exprType(expr); + + /* errors out if cast not possible */ + expr = coerce_expr_flexible(pstate, expr, source_oid, target_oid, + t_typmod, true); + + return expr; + } +} + +/* + * Helper function to coerce an expression to the target type. If + * no direct cast exists, it attempts to cast through text if the + * source or target type is agtype. This improves interoperability + * with types from other extensions. + */ +static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, + Oid source_oid, Oid target_oid, + int32 t_typmod, bool error_out) +{ + const Oid text_oid = TEXTOID; + Node *result; + + if (expr == NULL) + return NULL; + + /* Try a direct cast */ + result = coerce_to_target_type(pstate, expr, source_oid, target_oid, + t_typmod, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (result != NULL) + return result; + + /* Try cast via TEXT if either side is AGTYPE */ + if (source_oid == AGTYPEOID || target_oid == AGTYPEOID) + { + Node *to_text = coerce_to_target_type(pstate, expr, source_oid, text_oid, + -1, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (to_text != NULL) + { + result = coerce_to_target_type(pstate, to_text, text_oid, target_oid, + t_typmod, COERCION_EXPLICIT, + COERCE_EXPLICIT_CAST, -1); + if (result != NULL) + return result; + } + } + + if (error_out) + { + ereport(ERROR, + (errmsg_internal("typecast \'%s\' not supported", + format_type_be(target_oid)))); + } + + return NULL; +} + +static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, + FuncCall *fn, List *targs, + Form_pg_proc procform, + const char *extension) +{ + ParseState *pstate = &cpstate->pstate; + FuncExpr *fexpr = NULL; + Node *retval = NULL; + Node *last_srf = pstate->p_last_srf; + Oid *proargtypes; + + /* make sure procform in not NULL */ + Assert(procform != NULL); + proargtypes = procform->proargtypes.values; + + /* cast the agtype arguments to the types accepted by function */ + targs = cast_agtype_args_to_target_type(cpstate, procform, targs, proargtypes); + + /* now get the function node for the external function */ + fexpr = (FuncExpr *)ParseFuncOrColumn(pstate, fn->funcname, targs, + last_srf, fn, false, + fn->location); + pfree(procform); + + /* + * This will cast TEXT output to AGTYPE. It will error out if this is + * not possible to do. For TEXT to AGTYPE we need to wrap the output + * due to issues with creating a cast from TEXT to AGTYPE. + */ + if (fexpr->funcresulttype == TEXTOID) + { + retval = wrap_text_output_to_agtype(cpstate, fexpr); + } + else + { + retval = (Node *)fexpr; + } + + /* additional casts or wraps can be done here for other types */ + + /* flag that an aggregate was found during a transform */ + if (retval != NULL && retval->type == T_Aggref) + { + cpstate->exprHasAgg = true; + } + + /* we can just return it here */ + return retval; +} + +/* + * Cast a function's input parameter list from agtype to that function's input + * type. This is used for functions that don't take agtype as input and where + * there isn't an implicit cast to do this for us. + */ +static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, + Form_pg_proc procform, + List *fargs, + Oid *target_types) +{ + char *funcname = NameStr(procform->proname); + int nargs = procform->pronargs; + int given_nargs = list_length(fargs); + ListCell *lc = NULL; + + /* verify the length of args are same */ + if (given_nargs != nargs) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("function %s requires %d arguments, %d given", + funcname, nargs, given_nargs))); + } + + /* iterate through the function's args */ + foreach (lc, fargs) + { + Node *expr = lfirst(lc); + Oid source_oid = exprType(expr); + Oid target_oid = target_types[foreach_current_index(lc)]; + + if (source_oid == target_oid) + { + continue; + } + + /* errors out if cast not possible */ + expr = coerce_expr_flexible(&cpstate->pstate, expr, source_oid, + target_oid, -1, true); + + lfirst(lc) = expr; + } + + return fargs; +} + +/* + * Due to issues with creating a cast from text to agtype, we need to wrap a + * function that outputs text with text_to_agtype. + */ +static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, + FuncExpr *fexpr) +{ + Oid func_oid; + FuncExpr *retval; + + if (fexpr->funcresulttype != TEXTOID) + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("can only wrap text to agtype"))); + } + + func_oid = get_text_to_agtype_oid(); + retval = makeFuncExpr(func_oid, AGTYPEOID, list_make1(fexpr), InvalidOid, + InvalidOid, COERCE_SQL_SYNTAX); + retval->location = fexpr->location; + + /* return the wrapped function */ + return (Node *)retval; +} + +/* + * Returns Form_pg_proc struct for given function, if the function + * is not in search path, it is not considered. + */ +static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) +{ + CatCList *catlist = NULL; + Form_pg_proc procform = NULL; + Form_pg_proc result = NULL; + int nargs; + int i = 0; + List *asp = NIL; + bool asp_fetched = false; + bool found = false; + char *funcname = (((String*)linitial(fn->funcname))->sval); + int funcname_len = strlen(funcname); + + /* get a list of matching functions */ + catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + + if (catlist->n_members == 0) + { + ReleaseSysCacheList(catlist); + return NULL; + } + + nargs = list_length(fn->args); + + /* iterate through them and verify that they are in the search path */ + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple proctup = &catlist->members[i]->tuple; + procform = (Form_pg_proc) GETSTRUCT(proctup); + + /* + * Check if the function name, number of arguments, and + * variadic match before checking if it is in the search + * path. + */ + if (nargs == procform->pronargs && + fn->func_variadic == procform->provariadic && + strnlen(NameStr(procform->proname), NAMEDATALEN) == funcname_len && + pg_strcasecmp(funcname, procform->proname.data) == 0) + { + if (!asp_fetched) + { + asp = fetch_search_path(false); + asp_fetched = true; + } + + if (list_member_oid(asp, procform->pronamespace) && + !isTempNamespace(procform->pronamespace)) + { + found = true; + } + } + + if (found) + { + result = palloc(sizeof(FormData_pg_proc)); + memcpy(result, procform, sizeof(FormData_pg_proc)); + break; + } + + /* reset procform */ + procform = NULL; + } + + /* Error out if function not found */ + if (err_not_found && (result == NULL)) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("function %s does not exist", funcname), + errhint("If the function is from an external extension, " + "make sure the extension is installed and the " + "function is in the search path."))); + } + + /* we need to release the cache list */ + ReleaseSysCacheList(catlist); + list_free(asp); + + return result; +} + +static const char *get_mapped_extension(Oid func_oid) +{ + Oid extension_oid; + char *extension = NULL; + function_extension_cache_entry *entry; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + if (!entry->has_extension) + { + return NULL; + } + return NameStr(entry->extension); + } + + extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); + extension = get_extension_name(extension_oid); + + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension); + } + } + + if (extension != NULL) + { + pfree(extension); + } + + return entry->has_extension ? NameStr(entry->extension) : NULL; +} + +static bool function_belongs_to_extension(Oid func_oid, const char *extension) +{ + Oid extension_oid; + char *extension_name = NULL; + function_extension_cache_entry *entry; + bool belongs; + bool found; + + initialize_function_extension_cache(); + + entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->has_extension && + pg_strcasecmp(NameStr(entry->extension), extension) == 0; + } + + extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); + extension_name = get_extension_name(extension_oid); + + if (function_extension_cache == NULL) + { + initialize_function_extension_cache(); + } + + entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, + &found); + if (!found) + { + entry->has_extension = (extension_name != NULL); + if (entry->has_extension) + { + namestrcpy(&entry->extension, extension_name); + } + } + + belongs = (extension_name != NULL && + pg_strcasecmp(extension_name, extension) == 0); + + if (extension_name != NULL) + { + pfree(extension_name); + } + + return belongs; +} + +static bool is_extension_external(const char *extension) +{ + return ((extension != NULL) && + (pg_strcasecmp(extension, "age") != 0)); +} + +static bool function_needs_graph_name_argument(const char *name, int name_len) +{ + switch (name[0]) + { + case 'e': + return name_len == 7 && memcmp(name, "endNode", 7) == 0; + case 's': + return name_len == 9 && memcmp(name, "startNode", 9) == 0; + case 'v': + return (name_len == 3 && memcmp(name, "vle", 3) == 0) || + (name_len == 12 && + memcmp(name, "vertex_stats", 12) == 0); + default: + return false; + } +} + +/* Returns age_ prefiexed lower case function name */ +static char *construct_age_function_name(char *funcname, int funcname_len) +{ + char *ag_name = palloc(funcname_len + 5); + int i; + + /* copy in the prefix - all AGE functions are prefixed with age_ */ + memcpy(ag_name, "age_", 4); + + /* + * All AGE function names are in lower case. So, copy in the funcname + * in lower case. + */ + for (i = 0; i < funcname_len; i++) + { + ag_name[i + 4] = tolower(funcname[i]); + } + + /* terminate it with 0 */ + ag_name[i + 4] = 0; + + return ag_name; +} + + +/* + * Checks if a function exists. If the extension name is given, + * then it checks if the function exists in that extension. + */ +static bool function_exists(char *funcname, char *extension) +{ + CatCList *catlist = NULL; + bool found = false; + function_exists_cache_key key; + function_exists_cache_entry *entry; + bool cache_found; + int i = 0; + + initialize_function_exists_cache(); + MemSet(&key, 0, sizeof(key)); + namestrcpy(&key.funcname, funcname); + if (extension != NULL) + { + namestrcpy(&key.extension, extension); + } + + entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); + if (entry != NULL) + { + return entry->exists; + } + + /* get a list of matching functions */ + catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + + if (catlist->n_members == 0) + { + ReleaseSysCacheList(catlist); + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = false; + return false; + } + else if (extension == NULL) + { + ReleaseSysCacheList(catlist); + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = true; + return true; + } + + for (i = 0; i < catlist->n_members; i++) + { + HeapTuple proctup = &catlist->members[i]->tuple; + Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + + if (function_belongs_to_extension(procform->oid, extension)) + { + found = true; + break; + } + } + + /* we need to release the cache list */ + ReleaseSysCacheList(catlist); + + entry = hash_search(function_exists_cache, &key, HASH_ENTER, + &cache_found); + entry->exists = found; + + return found; +} + +static void initialize_function_exists_cache(void) +{ + HASHCTL hash_ctl; + + if (function_exists_cache != NULL) + { + return; + } + + initialize_function_caches(); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(function_exists_cache_key); + hash_ctl.entrysize = sizeof(function_exists_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_exists_cache = hash_create("cypher function existence cache", 16, + &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void initialize_function_extension_cache(void) +{ + HASHCTL hash_ctl; + + if (function_extension_cache != NULL) + { + return; + } + + initialize_function_caches(); + + MemSet(&hash_ctl, 0, sizeof(hash_ctl)); + hash_ctl.keysize = sizeof(Oid); + hash_ctl.entrysize = sizeof(function_extension_cache_entry); + hash_ctl.hcxt = CacheMemoryContext; + + function_extension_cache = + hash_create("cypher function extension cache", 16, &hash_ctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); +} + +static void initialize_function_caches(void) +{ + if (!CacheMemoryContext) + { + CreateCacheMemoryContext(); + } + + if (!function_cache_callback_registered) + { + CacheRegisterSyscacheCallback(PROCOID, invalidate_function_caches, + (Datum)0); + CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, + invalidate_function_caches, + (Datum)0); + function_cache_callback_registered = true; + } +} + +static void invalidate_function_caches(Datum arg, int cache_id, + uint32 hash_value) +{ + if (function_exists_cache != NULL) + { + hash_destroy(function_exists_cache); + function_exists_cache = NULL; + } + + if (function_extension_cache != NULL) + { + hash_destroy(function_extension_cache); + function_extension_cache = NULL; + } + + agtype_access_operator_oid = InvalidOid; + agtype_access_slice_oid = InvalidOid; + agtype_in_operator_oid = InvalidOid; + agtype_add_oid = InvalidOid; + agtype_build_empty_list_oid = InvalidOid; + agtype_build_list_oid = InvalidOid; + agtype_build_empty_map_oid = InvalidOid; + agtype_build_map_oid = InvalidOid; + agtype_build_map_nonull_oid = InvalidOid; + age_properties_oid = InvalidOid; + agtype_string_match_starts_with_oid = InvalidOid; + agtype_string_match_ends_with_oid = InvalidOid; + agtype_string_match_contains_oid = InvalidOid; + text_to_agtype_oid = InvalidOid; + age_vle_path_length_oid = InvalidOid; + age_vle_path_node_count_oid = InvalidOid; + age_vle_edge_tail_count_oid = InvalidOid; + age_vle_list_is_empty_oid = InvalidOid; + age_vle_list_slice_count_oid = InvalidOid; + age_vle_list_slice_is_empty_oid = InvalidOid; + age_materialize_vle_list_slice_oid = InvalidOid; + age_materialize_vle_slice_boundary_oid = InvalidOid; + age_materialize_vle_edges_oid = InvalidOid; + age_materialize_vle_nodes_oid = InvalidOid; + age_materialize_vle_node_at_oid = InvalidOid; + age_materialize_vle_node_tail_last_oid = InvalidOid; + age_vle_node_tail_last_id_oid = InvalidOid; + age_vle_node_id_at_oid = InvalidOid; + age_vle_node_label_at_oid = InvalidOid; + age_vle_node_labels_at_oid = InvalidOid; + age_vle_node_properties_at_oid = InvalidOid; + age_materialize_vle_nodes_slice_oid = InvalidOid; + age_materialize_vle_nodes_tail_oid = InvalidOid; + age_materialize_vle_nodes_reversed_oid = InvalidOid; + age_materialize_vle_edge_at_oid = InvalidOid; + age_materialize_vle_edge_reversed_at_oid = InvalidOid; + age_materialize_vle_edge_tail_last_oid = InvalidOid; + age_vle_edge_tail_last_id_oid = InvalidOid; + age_vle_tail_last_field_oid = InvalidOid; + age_vle_tail_last_edge_endpoint_oid = InvalidOid; + age_vle_tail_last_endpoint_field_oid = InvalidOid; + age_vle_edge_id_at_oid = InvalidOid; + age_vle_edge_index_exists_oid = InvalidOid; + age_vle_edge_indices_equal_oid = InvalidOid; + age_vle_edge_reversed_index_equal_oid = InvalidOid; + age_vle_edge_label_at_oid = InvalidOid; + age_vle_edge_properties_at_oid = InvalidOid; + age_vle_edge_start_node_at_oid = InvalidOid; + age_vle_edge_end_node_at_oid = InvalidOid; + age_vle_edge_endpoint_field_at_oid = InvalidOid; + age_vle_edge_start_id_at_oid = InvalidOid; + age_vle_edge_end_id_at_oid = InvalidOid; + age_materialize_vle_edges_tail_oid = InvalidOid; + age_materialize_vle_edges_reversed_oid = InvalidOid; +} + +static Oid get_agtype_access_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_operator_oid)) + { + agtype_access_operator_oid = + get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); + } + + return agtype_access_operator_oid; +} + +static Oid get_agtype_access_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_access_slice_oid)) + { + agtype_access_slice_oid = + get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, AGTYPEOID, + AGTYPEOID); + } + + return agtype_access_slice_oid; +} + +static Oid get_agtype_in_operator_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_in_operator_oid)) + { + agtype_in_operator_oid = + get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_in_operator_oid; +} + +static Oid get_agtype_add_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_add_oid)) + { + agtype_add_oid = + get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + } + + return agtype_add_oid; +} + +static Oid get_agtype_build_empty_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_list_oid)) + { + agtype_build_empty_list_oid = + get_ag_func_oid("agtype_build_list", 0); + } + + return agtype_build_empty_list_oid; +} + +static Oid get_agtype_build_list_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_list_oid)) + { + agtype_build_list_oid = + get_ag_func_oid("agtype_build_list", 1, ANYOID); + } + + return agtype_build_list_oid; +} + +static Oid get_agtype_build_empty_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_empty_map_oid)) + { + agtype_build_empty_map_oid = get_ag_func_oid("agtype_build_map", 0); + } + + return agtype_build_empty_map_oid; +} + +static Oid get_agtype_build_map_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_oid)) + { + agtype_build_map_oid = + get_ag_func_oid("agtype_build_map", 1, ANYOID); + } + + return agtype_build_map_oid; +} + +static Oid get_agtype_build_map_nonull_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_build_map_nonull_oid)) + { + agtype_build_map_nonull_oid = + get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + } + + return agtype_build_map_nonull_oid; +} + +static Oid get_age_properties_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_properties_oid)) + { + age_properties_oid = get_ag_func_oid("age_properties", 1, AGTYPEOID); + } + + return age_properties_oid; +} + +static Oid get_agtype_string_match_starts_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_starts_with_oid)) + { + agtype_string_match_starts_with_oid = + get_ag_func_oid("agtype_string_match_starts_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_starts_with_oid; +} + +static Oid get_agtype_string_match_ends_with_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_ends_with_oid)) + { + agtype_string_match_ends_with_oid = + get_ag_func_oid("agtype_string_match_ends_with", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_ends_with_oid; +} + +static Oid get_agtype_string_match_contains_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(agtype_string_match_contains_oid)) + { + agtype_string_match_contains_oid = + get_ag_func_oid("agtype_string_match_contains", 2, AGTYPEOID, + AGTYPEOID); + } + + return agtype_string_match_contains_oid; +} + +static Oid get_text_to_agtype_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(text_to_agtype_oid)) + { + text_to_agtype_oid = get_ag_func_oid("text_to_agtype", 1, TEXTOID); + } + + return text_to_agtype_oid; +} + +static Oid get_age_vle_path_length_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_path_length_oid)) + { + age_vle_path_length_oid = + get_ag_func_oid("age_vle_path_length", 1, AGTYPEOID); + } + + return age_vle_path_length_oid; +} + +static Oid get_age_vle_path_node_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_path_node_count_oid)) + { + age_vle_path_node_count_oid = + get_ag_func_oid("age_vle_path_node_count", 1, AGTYPEOID); + } + + return age_vle_path_node_count_oid; +} + +static Oid get_age_vle_edge_tail_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_tail_count_oid)) + { + age_vle_edge_tail_count_oid = + get_ag_func_oid("age_vle_edge_tail_count", 1, AGTYPEOID); + } + + return age_vle_edge_tail_count_oid; +} + +static Oid get_age_vle_list_is_empty_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_is_empty_oid)) + { + age_vle_list_is_empty_oid = + get_ag_func_oid("age_vle_list_is_empty", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_list_is_empty_oid; +} + +static Oid get_age_vle_list_slice_count_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_slice_count_oid)) + { + age_vle_list_slice_count_oid = + get_ag_func_oid("age_vle_list_slice_count", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_list_slice_count_oid; +} + +static Oid get_age_vle_list_slice_is_empty_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_list_slice_is_empty_oid)) + { + age_vle_list_slice_is_empty_oid = + get_ag_func_oid("age_vle_list_slice_is_empty", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_list_slice_is_empty_oid; +} + +static Oid get_age_materialize_vle_list_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_list_slice_oid)) + { + age_materialize_vle_list_slice_oid = + get_ag_func_oid("age_materialize_vle_list_slice", 4, AGTYPEOID, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_list_slice_oid; +} + +static Oid get_age_materialize_vle_slice_boundary_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_slice_boundary_oid)) + { + age_materialize_vle_slice_boundary_oid = + get_ag_func_oid("age_materialize_vle_slice_boundary", 4, + AGTYPEOID, AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_slice_boundary_oid; +} + +static Oid get_age_materialize_vle_edges_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_oid)) + { + age_materialize_vle_edges_oid = + get_ag_func_oid("age_materialize_vle_edges", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_oid; +} + +static Oid get_age_materialize_vle_nodes_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_oid)) + { + age_materialize_vle_nodes_oid = + get_ag_func_oid("age_materialize_vle_nodes", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_oid; +} + +static Oid get_age_materialize_vle_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_node_at_oid)) + { + age_materialize_vle_node_at_oid = + get_ag_func_oid("age_materialize_vle_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_materialize_vle_node_at_oid; +} + +static Oid get_age_materialize_vle_node_tail_last_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_node_tail_last_oid)) + { + age_materialize_vle_node_tail_last_oid = + get_ag_func_oid("age_materialize_vle_node_tail_last", 1, + AGTYPEOID); + } + + return age_materialize_vle_node_tail_last_oid; +} + +static Oid get_age_vle_node_tail_last_id_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_tail_last_id_oid)) + { + age_vle_node_tail_last_id_oid = + get_ag_func_oid("age_vle_node_tail_last_id", 1, AGTYPEOID); + } + + return age_vle_node_tail_last_id_oid; +} + +static Oid get_age_vle_node_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_id_at_oid)) + { + age_vle_node_id_at_oid = + get_ag_func_oid("age_vle_node_id_at", 2, AGTYPEOID, AGTYPEOID); + } + + return age_vle_node_id_at_oid; +} + +static Oid get_age_vle_node_label_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_label_at_oid)) + { + age_vle_node_label_at_oid = + get_ag_func_oid("age_vle_node_label_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_label_at_oid; +} + +static Oid get_age_vle_node_labels_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_labels_at_oid)) + { + age_vle_node_labels_at_oid = + get_ag_func_oid("age_vle_node_labels_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_labels_at_oid; +} + +static Oid get_age_vle_node_properties_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_node_properties_at_oid)) + { + age_vle_node_properties_at_oid = + get_ag_func_oid("age_vle_node_properties_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_node_properties_at_oid; +} + +static Oid get_age_materialize_vle_nodes_slice_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_slice_oid)) + { + age_materialize_vle_nodes_slice_oid = + get_ag_func_oid("age_materialize_vle_nodes_slice", 3, AGTYPEOID, + AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_nodes_slice_oid; +} + +static Oid get_age_materialize_vle_nodes_tail_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_tail_oid)) + { + age_materialize_vle_nodes_tail_oid = + get_ag_func_oid("age_materialize_vle_nodes_tail", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_tail_oid; +} + +static Oid get_age_materialize_vle_nodes_reversed_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_nodes_reversed_oid)) + { + age_materialize_vle_nodes_reversed_oid = get_ag_func_oid( + "age_materialize_vle_nodes_reversed", 1, AGTYPEOID); + } + + return age_materialize_vle_nodes_reversed_oid; +} + +static Oid get_age_materialize_vle_edge_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_at_oid)) + { + age_materialize_vle_edge_at_oid = + get_ag_func_oid("age_materialize_vle_edge_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_materialize_vle_edge_at_oid; +} + +static Oid get_age_materialize_vle_edge_reversed_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_reversed_at_oid)) + { + age_materialize_vle_edge_reversed_at_oid = + get_ag_func_oid("age_materialize_vle_edge_reversed_at", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_materialize_vle_edge_reversed_at_oid; +} + +static Oid get_age_materialize_vle_edge_tail_last_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edge_tail_last_oid)) + { + age_materialize_vle_edge_tail_last_oid = + get_ag_func_oid("age_materialize_vle_edge_tail_last", 1, + AGTYPEOID); + } + + return age_materialize_vle_edge_tail_last_oid; +} + +static Oid get_age_vle_edge_tail_last_id_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_tail_last_id_oid)) + { + age_vle_edge_tail_last_id_oid = + get_ag_func_oid("age_vle_edge_tail_last_id", 1, AGTYPEOID); + } + + return age_vle_edge_tail_last_id_oid; +} + +static Oid get_age_vle_tail_last_field_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_field_oid)) + { + age_vle_tail_last_field_oid = + get_ag_func_oid("age_vle_tail_last_field", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_tail_last_field_oid; +} + +static Oid get_age_vle_tail_last_edge_endpoint_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_edge_endpoint_oid)) + { + age_vle_tail_last_edge_endpoint_oid = + get_ag_func_oid("age_vle_tail_last_edge_endpoint", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_tail_last_edge_endpoint_oid; +} + +static Oid get_age_vle_tail_last_endpoint_field_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_tail_last_endpoint_field_oid)) + { + age_vle_tail_last_endpoint_field_oid = + get_ag_func_oid("age_vle_tail_last_endpoint_field", 2, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_tail_last_endpoint_field_oid; +} + +static Oid get_age_vle_edge_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_id_at_oid)) + { + age_vle_edge_id_at_oid = + get_ag_func_oid("age_vle_edge_id_at", 2, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_id_at_oid; +} + +static Oid get_age_vle_edge_index_exists_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_index_exists_oid)) + { + age_vle_edge_index_exists_oid = + get_ag_func_oid("age_vle_edge_index_exists", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_index_exists_oid; +} + +static Oid get_age_vle_edge_indices_equal_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_indices_equal_oid)) + { + age_vle_edge_indices_equal_oid = + get_ag_func_oid("age_vle_edge_indices_equal", 3, AGTYPEOID, + AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_indices_equal_oid; +} + +static Oid get_age_vle_edge_reversed_index_equal_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_reversed_index_equal_oid)) + { + age_vle_edge_reversed_index_equal_oid = + get_ag_func_oid("age_vle_edge_reversed_index_equal", 3, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_reversed_index_equal_oid; +} + +static Oid get_age_vle_edge_label_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_label_at_oid)) + { + age_vle_edge_label_at_oid = + get_ag_func_oid("age_vle_edge_label_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_label_at_oid; +} + +static Oid get_age_vle_edge_properties_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_properties_at_oid)) + { + age_vle_edge_properties_at_oid = + get_ag_func_oid("age_vle_edge_properties_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_properties_at_oid; +} + +static Oid get_age_vle_edge_start_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_start_node_at_oid)) + { + age_vle_edge_start_node_at_oid = + get_ag_func_oid("age_vle_edge_start_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_start_node_at_oid; +} + +static Oid get_age_vle_edge_end_node_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_end_node_at_oid)) + { + age_vle_edge_end_node_at_oid = + get_ag_func_oid("age_vle_edge_end_node_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_end_node_at_oid; +} + +static Oid get_age_vle_edge_endpoint_field_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_endpoint_field_at_oid)) + { + age_vle_edge_endpoint_field_at_oid = + get_ag_func_oid("age_vle_edge_endpoint_field_at", 3, + AGTYPEOID, AGTYPEOID, AGTYPEOID); + } + + return age_vle_edge_endpoint_field_at_oid; +} + +static Oid get_age_vle_edge_start_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_start_id_at_oid)) + { + age_vle_edge_start_id_at_oid = + get_ag_func_oid("age_vle_edge_start_id_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_start_id_at_oid; +} + +static Oid get_age_vle_edge_end_id_at_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_vle_edge_end_id_at_oid)) + { + age_vle_edge_end_id_at_oid = + get_ag_func_oid("age_vle_edge_end_id_at", 2, AGTYPEOID, + AGTYPEOID); + } + + return age_vle_edge_end_id_at_oid; +} + +static Oid get_age_materialize_vle_edges_tail_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_tail_oid)) + { + age_materialize_vle_edges_tail_oid = + get_ag_func_oid("age_materialize_vle_edges_tail", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_tail_oid; +} + +static Oid get_age_materialize_vle_edges_reversed_oid(void) +{ + initialize_function_caches(); + + if (!OidIsValid(age_materialize_vle_edges_reversed_oid)) + { + age_materialize_vle_edges_reversed_oid = get_ag_func_oid( + "age_materialize_vle_edges_reversed", 1, AGTYPEOID); + } + + return age_materialize_vle_edges_reversed_oid; +} + +static Expr *get_current_single_vle_path_expr(cypher_parsestate *cpstate, + Node *arg) +{ + ColumnRef *cr = NULL; + String *field = NULL; + transform_entity *entity = NULL; + cypher_path *path = NULL; + cypher_relationship *rel = NULL; + FuncExpr *path_expr = NULL; + + if (!IsA(arg, ColumnRef)) + { + return NULL; + } + + cr = (ColumnRef *)arg; + if (list_length(cr->fields) != 1 || !IsA(linitial(cr->fields), String)) + { + return NULL; + } + + field = linitial(cr->fields); + entity = find_variable(cpstate, strVal(field)); + if (entity == NULL || entity->type != ENT_PATH || + !entity->declared_in_current_clause || + entity->expr == NULL || !IsA(entity->expr, FuncExpr)) + { + return NULL; + } + + path = entity->entity.path; + if (path == NULL || list_length(path->path) != 3) + { + return NULL; + } + + rel = (cypher_relationship *)lfirst(lnext(path->path, + list_head(path->path))); + if (rel == NULL || rel->varlen == NULL) + { + return NULL; + } + + path_expr = (FuncExpr *)entity->expr; + if (list_length(path_expr->args) == 1) + { + return linitial(path_expr->args); + } + else if (list_length(path_expr->args) == 3) + { + return lsecond(path_expr->args); + } + + return NULL; +} + +static Expr *get_current_vle_edge_expr(cypher_parsestate *cpstate, Node *arg) +{ + ColumnRef *cr = NULL; + String *field = NULL; + transform_entity *entity = NULL; + + if (!IsA(arg, ColumnRef)) + { + return NULL; + } + + cr = (ColumnRef *)arg; + if (list_length(cr->fields) != 1 || !IsA(linitial(cr->fields), String)) + { + return NULL; + } + + field = linitial(cr->fields); + entity = find_variable(cpstate, strVal(field)); + if (entity == NULL || entity->type != ENT_VLE_EDGE || + !entity->declared_in_current_clause || entity->expr == NULL) + { + return NULL; + } + + return entity->expr; +} + +static Expr *get_current_vle_relationship_list_expr(cypher_parsestate *cpstate, + Node *arg) +{ + FuncCall *fn = NULL; + + if (!IsA(arg, FuncCall)) + { + return get_current_vle_edge_expr(cpstate, arg); + } + + fn = (FuncCall *)arg; + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "relationships") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + return get_current_single_vle_path_expr(cpstate, linitial(fn->args)); +} + +static Node *try_transform_vle_path_length(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "length") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + func_oid = get_age_vle_path_length_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_id_access(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_path_tail_last_list(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name) +{ + FuncCall *last_fn = NULL; + FuncCall *tail_fn = NULL; + FuncCall *list_fn = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + last_fn = (FuncCall *)node; + if (list_length(last_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(last_fn->funcname)), "last") != 0 || + list_length(last_fn->args) != 1 || + !IsA(linitial(last_fn->args), FuncCall)) + { + return false; + } + + tail_fn = (FuncCall *)linitial(last_fn->args); + if (list_length(tail_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(tail_fn->funcname)), "tail") != 0 || + list_length(tail_fn->args) != 1) + { + return false; + } + + if (IsA(linitial(tail_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(tail_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(tail_fn->args)); + if (*vle_expr != NULL) + { + *list_name = "relationships"; + } + } + + return *vle_expr != NULL; +} + +static bool parse_vle_tail_last_endpoint(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + bool *start_endpoint) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + endpoint_fn = (FuncCall *)node; + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return false; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + *start_endpoint = true; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + *start_endpoint = false; + } + else + { + return false; + } + + return parse_vle_path_tail_last_list(cpstate, + linitial(endpoint_fn->args), + vle_expr, &list_name) && + pg_strcasecmp(list_name, "relationships") == 0; +} + +static Node *try_transform_vle_path_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Node *nested_expr = NULL; + Oid func_oid; + int64 index; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name)) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_tail_last_id_oid(); + } + else + { + func_oid = get_age_vle_edge_tail_last_id_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (IsA(linitial(fn->args), FuncCall)) + { + nested_expr = transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(fn->args), 8); + if (nested_expr != NULL) + { + return nested_expr; + } + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index)) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool start_endpoint; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + if (!parse_vle_edge_endpoint_index(cpstate, linitial(fn->args), &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (start_endpoint) + { + func_oid = get_age_vle_edge_start_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 56; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 64; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(endpoint_fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(endpoint_fn->args), + &vle_expr, &list_name) && + pg_strcasecmp(list_name, "relationships") == 0) + { + mode = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 2 : 3; + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_edge_endpoint_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, + linitial(endpoint_fn->args), + &vle_expr, &list_name, &index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + func_oid = get_age_vle_edge_start_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_id_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_id_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + FuncCall *boundary_fn = NULL; + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 56; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 64; + } + else + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(endpoint_fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_path_endpoint_access(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + bool start_endpoint; + + if (!parse_vle_edge_endpoint_index(cpstate, (Node *)fn, &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (start_endpoint) + { + func_oid = get_age_vle_edge_start_node_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_node_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 40; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 48; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *endpoint_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name) && + pg_strcasecmp(list_name, "relationships") == 0) + { + mode = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 0 : 1; + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_edge_endpoint_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + index_expr = make_agtype_integer_const(index, fn->location); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + func_oid = get_age_vle_edge_start_node_at_oid(); + } + else + { + func_oid = get_age_vle_edge_end_node_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_access( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + mode_offset = 40; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + mode_offset = 48; + } + else + { + return NULL; + } + + retval = transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(fn->args), mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, + (FuncCall *)linitial(fn->args), + mode_offset); +} + +static Node *try_transform_vle_path_endpoint_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + char *field_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + bool start_endpoint; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + if (!parse_vle_edge_endpoint_index(cpstate, linitial(fn->args), &vle_expr, + &index_expr, &start_endpoint)) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = start_endpoint ? 0 : 1; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = start_endpoint ? 2 : 3; + } + else + { + mode = start_endpoint ? 4 : 5; + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_edge_endpoint_field_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(vle_expr, index_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_nested_transform_index_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *field_name = NULL; + char *endpoint_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), A_Indirection)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 72 : 80; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 88 : 96; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? + 104 : 112; + } + else + { + return NULL; + } + + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(endpoint_fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *endpoint_fn = NULL; + FuncCall *boundary_fn = NULL; + char *field_name = NULL; + char *endpoint_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + endpoint_fn = (FuncCall *)linitial(fn->args); + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1 || + !IsA(linitial(endpoint_fn->args), FuncCall)) + { + return NULL; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 72 : 80; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? 88 : 96; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = pg_strcasecmp(endpoint_name, "startNode") == 0 ? + 104 : 112; + } + else + { + return NULL; + } + + if (pg_strcasecmp(endpoint_name, "startNode") != 0 && + pg_strcasecmp(endpoint_name, "endNode") != 0) + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(endpoint_fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_tail_last_endpoint_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + char *field_name = NULL; + Expr *vle_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + bool start_endpoint; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + if (!parse_vle_tail_last_endpoint(cpstate, linitial(fn->args), &vle_expr, + &start_endpoint)) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = start_endpoint ? 0 : 1; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = start_endpoint ? 2 : 3; + } + else + { + mode = start_endpoint ? 4 : 5; + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_endpoint_field_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_edge_variable_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + char *field_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "properties") != 0 && + pg_strcasecmp(field_name, "type") != 0 && + pg_strcasecmp(field_name, "label") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (vle_expr == NULL) + { + return NULL; + } + + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_edge_properties_at_oid(); + } + else + { + func_oid = get_age_vle_edge_label_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_node_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "nodes") != 0) + { + return NULL; + } + + if (pg_strcasecmp(field_name, "label") == 0) + { + func_oid = get_age_vle_node_label_at_oid(); + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + func_oid = get_age_vle_node_labels_at_oid(); + } + else + { + func_oid = get_age_vle_node_properties_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static bool parse_vle_path_boundary_list_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + char **list_name, + int64 *index) +{ + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + + if (!IsA(node, FuncCall)) + { + return false; + } + + outer_fn = (FuncCall *)node; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return false; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) + { + return false; + } + + if (!IsA(linitial(outer_fn->args), FuncCall)) + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (*vle_expr == NULL) + { + return false; + } + + *list_name = "relationships"; + *index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + return true; + } + + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return false; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + *list_name = inner_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + *index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + return *vle_expr != NULL; + } + + if ((pg_strcasecmp(inner_name, "tail") != 0 && + pg_strcasecmp(inner_name, "reverse") != 0) || + !IsA(linitial(inner_fn->args), FuncCall)) + { + return false; + } + + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") != 0) + { + return false; + } + *index = 1; + } + else + { + *index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + return *vle_expr != NULL; +} + +static bool parse_vle_edge_endpoint_index(cypher_parsestate *cpstate, + Node *node, Expr **vle_expr, + Node **index_expr, + bool *start_endpoint) +{ + FuncCall *endpoint_fn = NULL; + char *endpoint_name = NULL; + char *list_name = NULL; + int64 boundary_index; + + if (!IsA(node, FuncCall)) + { + return false; + } + + endpoint_fn = (FuncCall *)node; + if (list_length(endpoint_fn->funcname) != 1 || + list_length(endpoint_fn->args) != 1) + { + return false; + } + + endpoint_name = strVal(linitial(endpoint_fn->funcname)); + if (pg_strcasecmp(endpoint_name, "startNode") == 0) + { + *start_endpoint = true; + } + else if (pg_strcasecmp(endpoint_name, "endNode") == 0) + { + *start_endpoint = false; + } + else + { + return false; + } + + if (IsA(linitial(endpoint_fn->args), A_Indirection)) + { + A_Indirection *a_ind = linitial(endpoint_fn->args); + + if (parse_vle_path_indexed_list_index(cpstate, a_ind, vle_expr, + &list_name, index_expr)) + { + return pg_strcasecmp(list_name, "relationships") == 0; + } + + if (list_length(a_ind->indirection) == 1 && + IsA(linitial(a_ind->indirection), A_Indices)) + { + A_Indices *indices = linitial(a_ind->indirection); + + if (!indices->is_slice && indices->uidx != NULL) + { + *vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (*vle_expr != NULL) + { + *index_expr = transform_cypher_expr_recurse(cpstate, + indices->uidx); + return true; + } + } + } + + return false; + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(endpoint_fn->args), + vle_expr, &list_name, + &boundary_index) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return false; + } + + *index_expr = (Node *)make_agtype_integer_const( + boundary_index, exprLocation(linitial(endpoint_fn->args))); + + return true; +} + +static bool parse_vle_path_indexed_list_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Node **index_expr) +{ + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + char *outer_name = NULL; + int64 constant_index; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } + + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return false; + } + + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return false; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + *list_name = outer_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + if (*vle_expr == NULL) + { + return false; + } + + *index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + return true; + } + + if ((pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) || + !get_nonnegative_integer_const(indices->uidx, &constant_index)) + { + return false; + } + + if (IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return false; + } + + *list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(*list_name, "nodes") != 0 && + pg_strcasecmp(*list_name, "relationships") != 0) + { + return false; + } + + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (*vle_expr != NULL) + { + *list_name = "relationships"; + } + } + + if (*vle_expr == NULL) + { + return false; + } + + if (pg_strcasecmp(outer_name, "tail") == 0) + { + *index_expr = (Node *)make_agtype_integer_const( + constant_index + 1, exprLocation(indices->uidx)); + } + else + { + *index_expr = (Node *)make_agtype_integer_const( + -constant_index - 1, exprLocation(indices->uidx)); + } + + return true; +} + +static Node *try_transform_vle_path_boundary_field(cypher_parsestate *cpstate, + FuncCall *fn) +{ + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 index; + int64 mode; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "labels") != 0 && + pg_strcasecmp(field_name, "properties") != 0 && + pg_strcasecmp(field_name, "type") != 0) + { + return NULL; + } + + if (parse_vle_path_tail_last_list(cpstate, linitial(fn->args), &vle_expr, + &list_name)) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "label") == 0) + { + mode = 0; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode = 1; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode = 2; + } + else + { + return NULL; + } + } + else + { + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode = 3; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode = 4; + } + else + { + return NULL; + } + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_tail_last_field_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + + if (!parse_vle_path_boundary_list_index(cpstate, linitial(fn->args), + &vle_expr, &list_name, &index)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "label") == 0) + { + func_oid = get_age_vle_node_label_at_oid(); + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + func_oid = get_age_vle_node_labels_at_oid(); + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_node_properties_at_oid(); + } + else + { + return NULL; + } + } + else + { + if (pg_strcasecmp(field_name, "properties") == 0) + { + func_oid = get_age_vle_edge_properties_at_oid(); + } + else if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + func_oid = get_age_vle_edge_label_at_oid(); + } + else + { + return NULL; + } + } + + index_expr = make_agtype_integer_const(index, fn->location); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_boundary_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *boundary_fn = NULL; + char *field_name = NULL; + Node *retval = NULL; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode_offset = 16; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = 24; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = 32; + } + else + { + return NULL; + } + + boundary_fn = (FuncCall *)linitial(fn->args); + + retval = transform_vle_path_nested_transform_head_last( + cpstate, boundary_fn, mode_offset); + if (retval != NULL) + { + return retval; + } + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, + mode_offset); +} + +static Node *try_transform_vle_path_nested_transform_index_field( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *field_name = NULL; + int64 mode; + int64 mode_offset; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") == 0 || + pg_strcasecmp(field_name, "type") == 0) + { + mode_offset = 16; + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + mode_offset = 24; + } + else if (pg_strcasecmp(field_name, "properties") == 0) + { + mode_offset = 32; + } + else + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) + { + return NULL; + } + + if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (pg_strcasecmp(field_name, "type") == 0) + { + return NULL; + } + } + else if (pg_strcasecmp(field_name, "labels") == 0) + { + return NULL; + } + + mode_expr = make_agtype_integer_const(mode + mode_offset, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_edge_label(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *field_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + field_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(field_name, "label") != 0 && + pg_strcasecmp(field_name, "type") != 0) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + func_oid = get_age_vle_edge_label_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_edge_properties( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "properties") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_indexed_list_index(cpstate, a_ind, &vle_expr, + &list_name, &index_expr) || + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + func_oid = get_age_vle_edge_properties_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_size(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "size") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } + + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 3 : 1; + } + else + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } + +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; + } + + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_slice_count_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_slice_is_empty(cypher_parsestate *cpstate, + FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "isEmpty") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } + + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 3 : 1; + } + else + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 0; + } + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } + +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; + } + + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_slice_is_empty_oid(); + + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn, + int64 mode_offset) +{ + A_Indirection *a_ind = NULL; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *head_last_name = NULL; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; + bool last = false; + bool reverse = false; + bool tail_reverse = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) + { + return NULL; + } + + head_last_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(head_last_name, "head") != 0 && + pg_strcasecmp(head_last_name, "last") != 0) + { + return NULL; + } + last = pg_strcasecmp(head_last_name, "last") == 0; + + a_ind = (A_Indirection *)linitial(fn->args); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } + + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } + + if (IsA(a_ind->arg, FuncCall)) + { + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } + + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "nodes") == 0 || + pg_strcasecmp(outer_name, "relationships") == 0) + { + list_name = outer_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(outer_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 0; + } + else if ((pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) && + IsA(linitial(outer_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; } - case LITERAL_ENTRY: + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) { - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(elem->key), false, false); - val = transform_cypher_expr_recurse(cpstate, elem->value); - break; + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 4; + } + + double_tail = true; + goto check_vle_expr; } - case VARIABLE_SELECTOR: + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) { - char *key_str; - List *fields; + bool node_list = false; - Assert(IsA(elem->value, ColumnRef)); + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + } - /* Makes key from the ColumnRef's field */ - fields = ((ColumnRef *)elem->value)->fields; - key_str = strVal(lfirst(list_head(fields))); - key = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(key_str), false, false); + mode = node_list ? 6 : 4; + if (pg_strcasecmp(outer_name, "tail") == 0) + { + tail_reverse = true; + } + else + { + reverse = true; + } + goto check_vle_expr; + } - val = transform_cypher_expr_recurse(cpstate, elem->value); - break; + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; } - case ALL_PROPERTIES_SELECTOR: + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(outer_name, "tail") == 0) { - /* - * Key value pairs of the original map are added later outside - * the loop. Control never reaches this block. - */ - break; + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 6 : 4; } - default: + else { - elog(ERROR, "unknown map projection element type"); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 0; + reverse = true; + } + } + else if (pg_strcasecmp(outer_name, "tail") == 0 || + pg_strcasecmp(outer_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr != NULL) + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + mode = 4; + } + else + { + mode = 0; + reverse = true; + } } } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + mode = 0; + } - Assert(key); - Assert(val); - keyvals = lappend(lappend(keyvals, key), val); +check_vle_expr: + if (vle_expr == NULL) + { + return NULL; } - if (keyvals) + if (last) { - foid_agtype_build_map = get_agtype_build_map_nonull_oid(); - fexpr_new_map = makeFuncExpr(foid_agtype_build_map, AGTYPEOID, keyvals, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr_new_map->location = cmp->location; + mode++; } - /* - * In case .* is present, returns age_properties(cmp->map_var) + the new - * map. Else, returns the new map. - */ - if (has_all_prop_selector) + if (indices->lidx == NULL) { - if (!keyvals) - { - return (Node *)fexpr_orig_map; - } - else - { - return (Node *)make_op(pstate, list_make1(makeString("+")), - (Node *)fexpr_orig_map, - (Node *)fexpr_new_map, - pstate->p_last_srf, -1); - } + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); } else { - Assert(!has_all_prop_selector && fexpr_new_map); - return (Node *)fexpr_new_map; + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } + + if (indices->uidx == NULL) + { + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + } + + mode_expr = make_agtype_integer_const(mode + mode_offset + + (double_tail ? 360 : 0) + + (tail_reverse ? 240 : 0) + + (reverse ? 120 : 0), + fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Helper function to transform a cypher map into an agtype map. The function - * will use agtype_add to concatenate the argument list when the number of - * parameters (keys and values) exceeds 100, a PG limitation. - */ -static Node *transform_cypher_map(cypher_parsestate *cpstate, cypher_map *cm) +static Node *try_transform_vle_path_slice_head_last(cypher_parsestate *cpstate, + FuncCall *fn) { - ParseState *pstate = (ParseState *)cpstate; - List *newkeyvals_args = NIL; - ListCell *le = NULL; - FuncExpr *fexpr = NULL; - FuncExpr *aa_lhs_arg = NULL; - Oid abm_func_oid = InvalidOid; - Oid aa_func_oid = InvalidOid; - int nkeyvals = 0; - int i = 0; + return transform_vle_path_slice_head_last(cpstate, fn, 0); +} - /* get the number of keys and values */ - nkeyvals = list_length(cm->keyvals); +static Node *try_transform_vle_path_slice_boundary_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *boundary_fn = NULL; - /* error out if it isn't even */ - if (nkeyvals % 2 != 0) + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("number of keys does not match number of values"))); + return NULL; } - if (nkeyvals == 0) + boundary_fn = (FuncCall *)linitial(fn->args); + + return transform_vle_path_slice_head_last(cpstate, boundary_fn, 8); +} + +static Node *try_transform_vle_path_nested_transform_index_id_function( + cypher_parsestate *cpstate, FuncCall *fn) +{ + A_Indirection *a_ind = NULL; + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "id") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), A_Indirection)) { - abm_func_oid = get_agtype_build_empty_map_oid(); + return NULL; } - else if (!cm->keep_null) + + a_ind = (A_Indirection *)linitial(fn->args); + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) { - abm_func_oid = get_agtype_build_map_nonull_oid(); + return NULL; } - else + + mode_expr = make_agtype_integer_const(mode + 8, fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} + +static Node *try_transform_vle_path_size(cypher_parsestate *cpstate, + FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Oid func_oid; + bool tail_mode = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "size") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) { - abm_func_oid = get_agtype_build_map_oid(); + return NULL; } - /* get the concat function oid, if necessary */ - if (nkeyvals > 100) + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) { - aa_func_oid = get_agtype_add_oid(); + return NULL; } - /* get the key/val list */ - le = list_head(cm->keyvals); - /* while we have key/val to process */ - while (le != NULL) + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) { - Node *key = NULL; - Node *val = NULL; - Node *newval = NULL; - ParseCallbackState pcbstate; - Const *newkey = NULL; - - /* get the key */ - key = lfirst(le); - le = lnext(cm->keyvals, le); - /* get the value */ - val = lfirst(le); - le = lnext(cm->keyvals, le); - - /* transform the value */ - newval = transform_cypher_expr_recurse(cpstate, val); + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - /* - * If we have more than 50 key/value pairs, 100 elements, we will need - * to add in the list concatenation function. - */ - if (i >= 50) + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) { - /* build the object for the first 50 pairs for concat */ - fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, - InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + else if ((pg_strcasecmp(list_name, "tail") == 0 || + pg_strcasecmp(list_name, "reverse") == 0) && + list_length(list_fn->args) == 1) + { + FuncCall *base_fn = NULL; + char *base_name = NULL; - /* initial case, set up for concatenating 2 lists */ - if (aa_lhs_arg == NULL) + double_tail = pg_strcasecmp(inner_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(list_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) { - aa_lhs_arg = fexpr; + base_fn = (FuncCall *)linitial(list_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + list_name = base_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); } - /* - * For every other case, concatenate the list on to the previous - * concatenate operation. - */ else { - List *aa_args = list_make2(aa_lhs_arg, fexpr); - - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; - - /* set the lhs to the concatenation operation */ - aa_lhs_arg = fexpr; + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } } - - /* reset for the next 50 pairs */ - newkeyvals_args = NIL; - i = 0; - fexpr = NULL; - } - - /* build and append the transformed key/val pair */ - setup_parser_errposition_callback(&pcbstate, pstate, cm->location); - /* typtypmod, typcollation, typlen, and typbyval of agtype are */ - /* hard-coded. */ - newkey = makeConst(TEXTOID, -1, InvalidOid, -1, - CStringGetTextDatum(strVal(key)), false, false); - cancel_parser_errposition_callback(&pcbstate); - - newkeyvals_args = lappend(lappend(newkeyvals_args, newkey), newval); - - i++; + } + else + { + return NULL; + } + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + } + else + { + return NULL; } - /* now build the final map function */ - fexpr = makeFuncExpr(abm_func_oid, AGTYPEOID, newkeyvals_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cm->location; + if (vle_expr == NULL) + { + return NULL; + } - /* - * If there was a previous concatenation, build a final concatenation - * function node. - */ - if (aa_lhs_arg != NULL) + if (double_tail) { - List *aa_args = list_make2(aa_lhs_arg, fexpr); + Node *lower_expr = (Node *)make_agtype_integer_const(2, fn->location); + Node *upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + Const *mode_expr = make_agtype_integer_const( + pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0, fn->location); + + func_oid = get_age_vle_list_slice_count_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, + upper_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); + if (tail_mode && pg_strcasecmp(list_name, "relationships") == 0) + { + func_oid = get_age_vle_edge_tail_count_oid(); + } + else if (pg_strcasecmp(list_name, "nodes") == 0) + { + if (tail_mode) + { + func_oid = get_age_vle_path_length_oid(); + } + else + { + func_oid = get_age_vle_path_node_count_oid(); + } + } + else + { + func_oid = get_age_vle_path_length_oid(); } - return (Node *)fexpr; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Helper function to transform a cypher list into an agtype list. The function - * will use agtype_add to concatenate argument lists when the number of list - * elements, parameters, exceeds 100, a PG limitation. - */ -static Node *transform_cypher_list(cypher_parsestate *cpstate, cypher_list *cl) +static Node *try_transform_vle_path_is_empty(cypher_parsestate *cpstate, + FuncCall *fn) { - List *abl_args = NIL; - ListCell *le = NULL; - FuncExpr *aa_lhs_arg = NULL; - FuncExpr *fexpr = NULL; - Oid abl_func_oid = InvalidOid; - Oid aa_func_oid = InvalidOid; - int nelems = 0; - int i = 0; - - /* determine which build function we need */ - nelems = list_length(cl->elems); - if (nelems == 0) + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + bool tail_mode = false; + bool double_tail = false; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "isEmpty") != 0 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) { - abl_func_oid = get_agtype_build_empty_list_oid(); + return NULL; } - else + + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) { - abl_func_oid = get_agtype_build_list_oid(); + return NULL; } - /* get the concat function oid, if necessary */ - if (nelems > 100) + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) { - aa_func_oid = get_agtype_add_oid(); + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 0 : 1; } - - /* iterate through the list of elements */ - foreach (le, cl->elems) + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) { - Node *texpr = NULL; - - /* transform the argument */ - texpr = transform_cypher_expr_recurse(cpstate, lfirst(le)); + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - /* - * If we have more than 100 elements we will need to add in the list - * concatenation function. - */ - if (i >= 100) + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) { - /* build the list function node argument for concatenate */ - fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + } + else if ((pg_strcasecmp(list_name, "tail") == 0 || + pg_strcasecmp(list_name, "reverse") == 0) && + list_length(list_fn->args) == 1) + { + FuncCall *base_fn = NULL; + char *base_name = NULL; - /* initial case, set up for concatenating 2 lists */ - if (aa_lhs_arg == NULL) + double_tail = pg_strcasecmp(inner_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0; + tail_mode = pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(list_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) { - aa_lhs_arg = fexpr; + base_fn = (FuncCall *)linitial(list_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + list_name = base_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); } - /* - * For every other case, concatenate the list on to the previous - * concatenate operation. - */ else { - List *aa_args = list_make2(aa_lhs_arg, fexpr); - - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; - - /* set the lhs to the concatenation operation */ - aa_lhs_arg = fexpr; + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } } - - /* reset */ - abl_args = NIL; - i = 0; - fexpr = NULL; + } + else + { + return NULL; } - /* now add the latest transformed expression to the list */ - abl_args = lappend(abl_args, texpr); - i++; + if (tail_mode) + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 3; + } + else + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 0 : 1; + } + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + tail_mode = pg_strcasecmp(inner_name, "tail") == 0; + mode = tail_mode ? 3 : 1; + } + else + { + return NULL; } - /* now build the final list function */ - fexpr = makeFuncExpr(abl_func_oid, AGTYPEOID, abl_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - fexpr->location = cl->location; - - /* - * If there was a previous concatenation or list function, build a final - * concatenation function node - */ - if (aa_lhs_arg != NULL) + if (vle_expr == NULL) { - List *aa_args = list_make2(aa_lhs_arg, fexpr); + return NULL; + } - fexpr = makeFuncExpr(aa_func_oid, AGTYPEOID, aa_args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); + if (double_tail) + { + Node *lower_expr = (Node *)make_agtype_integer_const(2, fn->location); + Node *upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + + mode_expr = make_agtype_integer_const( + pg_strcasecmp(list_name, "nodes") == 0 ? 1 : 0, fn->location); + func_oid = get_age_vle_list_slice_is_empty_oid(); + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make4(vle_expr, lower_expr, + upper_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } - return (Node *)fexpr; + mode_expr = make_agtype_integer_const(mode, fn->location); + func_oid = get_age_vle_list_is_empty_oid(); + + return (Node *)makeFuncExpr(func_oid, BOOLOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* makes a VARIADIC agtype array */ -static ArrayExpr *make_agtype_array_expr(List *args) +static Node *try_transform_vle_path_head_last(cypher_parsestate *cpstate, + FuncCall *fn) { - ArrayExpr *newa = makeNode(ArrayExpr); + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + int64 index; + bool tail_last = false; - newa->elements = args; + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1) + { + return NULL; + } - /* assume all the variadic arguments were coerced to the same type */ - newa->element_typeid = AGTYPEOID; - newa->array_typeid = AGTYPEARRAYOID; + outer_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) + { + return NULL; + } - if (!OidIsValid(newa->array_typeid)) + if (!IsA(linitial(fn->args), FuncCall)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("could not find array type for data type %s", - format_type_be(newa->element_typeid)))); + vle_expr = get_current_vle_edge_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + list_name = "relationships"; + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + goto build_index_access; } - /* array_collid will be set by parse_collate.c */ - newa->multidims = false; + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } - return newa; -} + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) + { + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); -/* - * Transform a ColumnRef for indirection. Try to find the rte that the ColumnRef - * references and pass the properties of that rte as what the ColumnRef is - * referencing. Otherwise, reference the Var. - */ -static Node *transform_column_ref_for_indirection(cypher_parsestate *cpstate, - ColumnRef *cr) -{ - ParseState *pstate = (ParseState *)cpstate; - ParseNamespaceItem *pnsi = NULL; - Node *field1 = linitial(cr->fields); - char *relname = NULL; - Node *node = NULL; - int levels_up = 0; + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - Assert(IsA(field1, String)); - relname = strVal(field1); + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } - /* locate the referenced RTE (used to be find_rte(cpstate, relname)) */ - pnsi = refnameNamespaceItem(pstate, NULL, relname, cr->location, - &levels_up); + /* + * head(tail(list)) maps to original index 1. last(tail(list)) needs a + * tail-aware helper because it must return null when the tail is empty. + */ + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") == 0) + { + index = 1; + } + else + { + tail_last = true; + index = -1; + } + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } - /* - * If we didn't find anything, try looking for a previous variable - * reference. Otherwise, return NULL (colNameToVar will return NULL - * if nothing is found). - */ - if (!pnsi) + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else if (pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) + { + vle_expr = get_current_vle_edge_expr(cpstate, linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } + + list_name = "relationships"; + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") == 0) + { + index = 1; + } + else + { + tail_last = true; + index = -1; + } + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; + } + } + else + { + return NULL; + } + + if (vle_expr == NULL) { - Node *prev_var = colNameToVar(pstate, relname, false, cr->location); + return NULL; + } - return prev_var; + if (tail_last) + { + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_tail_last_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_tail_last_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } - /* find the properties column of the NSI and return a var for it */ - node = scanNSItemForColumn(pstate, pnsi, levels_up, "properties", - cr->location); +build_index_access: + index_expr = make_agtype_integer_const(index, fn->location); - /* - * If there's no "properties" column, continue transforming the - * ColumnRef as an agtype value and try to apply the indirection via - * agtype_access_operator. - */ - return node; + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_at_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_A_Indirection(cypher_parsestate *cpstate, - A_Indirection *a_ind) +static Node *transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn, int64 mode_offset) { - ParseState *pstate = &cpstate->pstate; - int location; - ListCell *lc = NULL; - Node *ind_arg_expr = NULL; - FuncExpr *func_expr = NULL; - Oid func_access_oid = InvalidOid; - Oid func_slice_oid = InvalidOid; - List *args = NIL; - bool is_access = false; - - /* validate that we have an indirection with at least 1 entry */ - Assert(a_ind != NULL && list_length(a_ind->indirection)); - /* get the agtype_access_operator function */ - func_access_oid = get_agtype_access_operator_oid(); - /* get the agtype_access_slice function */ - func_slice_oid = get_agtype_access_slice_oid(); + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *base_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *head_last_name = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *base_name = NULL; + int64 mode_flag = 0; + int64 mode; + bool last; + + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } - /* - * If the indirection argument is a ColumnRef, we want to pull out the - * properties, as a var node, if possible. - */ - if (IsA(a_ind->arg, ColumnRef)) + head_last_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(head_last_name, "head") != 0 && + pg_strcasecmp(head_last_name, "last") != 0) { - ColumnRef *cr = (ColumnRef *)a_ind->arg; + return NULL; + } + last = pg_strcasecmp(head_last_name, "last") == 0; - ind_arg_expr = transform_column_ref_for_indirection(cpstate, cr); + outer_fn = (FuncCall *)linitial(fn->args); + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) + { + return NULL; } - /* - * If we didn't get the properties from a ColumnRef, just transform the - * indirection argument. - */ - if (ind_arg_expr == NULL) + outer_name = strVal(linitial(outer_fn->funcname)); + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) { - ind_arg_expr = transform_cypher_expr_recurse(cpstate, a_ind->arg); + return NULL; } - ind_arg_expr = coerce_to_common_type(pstate, ind_arg_expr, AGTYPEOID, - "A_indirection"); + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 360; + } + else if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "reverse") == 0) + { + mode_flag = 240; + } + else if (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 120; + } + else + { + return NULL; + } - /* get the location of the expression */ - location = exprLocation(ind_arg_expr); + if (IsA(linitial(inner_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } - /* add the expression as the first entry */ - args = lappend(args, ind_arg_expr); + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } - /* iterate through the indirections */ - foreach (lc, a_ind->indirection) + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(base_fn->args)); + mode = (pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4) + mode_flag; + } + else { - Node *node = lfirst(lc); - - /* is this a slice? */ - if (IsA(node, A_Indices) && ((A_Indices *)node)->is_slice) + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(inner_fn->args)); + if (vle_expr == NULL) { - A_Indices *indices = (A_Indices *)node; + return NULL; + } + mode = 4 + mode_flag; + } - /* were we working on an access? if so, wrap and close it */ - if (is_access) - { - ArrayExpr *newa = make_agtype_array_expr(args); + if (vle_expr == NULL) + { + return NULL; + } - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, - list_make1(newa), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); + lower_expr = (Node *)make_agtype_integer_const(0, fn->location); + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + mode_expr = make_agtype_integer_const(mode + mode_offset + + (last ? 1 : 0), + fn->location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); - func_expr->funcvariadic = true; - func_expr->location = location; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - /* - * The result of this function is the input to the next access - * or slice operator. So we need to start out with a new arg - * list with this function expression. - */ - args = lappend(NIL, func_expr); +static Node *try_transform_vle_path_nested_transform_head_last( + cypher_parsestate *cpstate, FuncCall *fn) +{ + return transform_vle_path_nested_transform_head_last(cpstate, fn, 0); +} - /* we are no longer working on an access */ - is_access = false; +static Node *try_transform_vle_path_list_tail_reverse( + cypher_parsestate *cpstate, FuncCall *fn) +{ + FuncCall *inner_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + Expr *vle_expr = NULL; + Oid func_oid; - } + if (list_length(fn->funcname) != 1 || + list_length(fn->args) != 1 || + !IsA(linitial(fn->args), FuncCall)) + { + return NULL; + } - /* add slice bounds to args */ - if (!indices->lidx) - { - A_Const *n = makeNode(A_Const); - n->isnull = true; - n->location = -1; - node = transform_cypher_expr_recurse(cpstate, (Node *)n); - } - else - { - node = transform_cypher_expr_recurse(cpstate, indices->lidx); - } + outer_name = strVal(linitial(fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) + { + return NULL; + } - args = lappend(args, node); + inner_fn = (FuncCall *)linitial(fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return NULL; + } - if (!indices->uidx) - { - A_Const *n = makeNode(A_Const); - n->isnull = true; - n->location = -1; - node = transform_cypher_expr_recurse(cpstate, (Node *)n); - } - else - { - node = transform_cypher_expr_recurse(cpstate, indices->uidx); - } - args = lappend(args, node); + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") != 0 && + pg_strcasecmp(inner_name, "relationships") != 0) + { + return NULL; + } - /* wrap and close it */ - func_expr = makeFuncExpr(func_slice_oid, AGTYPEOID, args, - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->location = location; + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(inner_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - /* - * The result of this function is the input to the next access - * or slice operator. So we need to start out with a new arg - * list with this function expression. - */ - args = lappend(NIL, func_expr); + if (pg_strcasecmp(inner_name, "nodes") == 0) + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + func_oid = get_age_materialize_vle_nodes_tail_oid(); } - /* is this a string or index?*/ - else if (IsA(node, String) || IsA(node, A_Indices)) + else { - /* we are working on an access */ - is_access = true; - - /* is this an index? */ - if (IsA(node, A_Indices)) - { - A_Indices *indices = (A_Indices *)node; - - node = transform_cypher_expr_recurse(cpstate, indices->uidx); - args = lappend(args, node); - } - /* it must be a string */ - else - { - Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, - string_to_agtype(strVal(node)), - false, false); - args = lappend(args, const_str); - } + func_oid = get_age_materialize_vle_nodes_reversed_oid(); + } + } + else + { + if (pg_strcasecmp(outer_name, "tail") == 0) + { + func_oid = get_age_materialize_vle_edges_tail_oid(); } - /* not an indirection we understand */ else { - ereport(ERROR, - (errmsg("invalid indirection node %d", nodeTag(node)))); + func_oid = get_age_materialize_vle_edges_reversed_oid(); } } - /* if we were doing an access, we need wrap the args with access func. */ - if (is_access) - { - ArrayExpr *newa = make_agtype_array_expr(args); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, list_make1(newa), - InvalidOid, InvalidOid, - COERCE_EXPLICIT_CALL); - func_expr->funcvariadic = true; +static Node *try_transform_vle_path_relationships(cypher_parsestate *cpstate, + FuncCall *fn) +{ + Expr *vle_expr = NULL; + Oid func_oid; + + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "relationships") != 0 || + list_length(fn->args) != 1) + { + return NULL; } - Assert(func_expr != NULL); - func_expr->location = location; + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - return (Node *)func_expr; + func_oid = get_age_materialize_vle_edges_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_cypher_string_match(cypher_parsestate *cpstate, - cypher_string_match *csm_node) +static Node *try_transform_vle_path_nodes(cypher_parsestate *cpstate, + FuncCall *fn) { - Node *expr; - FuncExpr *func_expr; - Oid func_access_oid; - List *args = NIL; + Expr *vle_expr = NULL; + Oid func_oid; - switch (csm_node->operation) + if (list_length(fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(fn->funcname)), "nodes") != 0 || + list_length(fn->args) != 1) { - case CSMO_STARTS_WITH: - func_access_oid = get_agtype_string_match_starts_with_oid(); - break; - case CSMO_ENDS_WITH: - func_access_oid = get_agtype_string_match_ends_with_oid(); - break; - case CSMO_CONTAINS: - func_access_oid = get_agtype_string_match_contains_oid(); - break; + return NULL; + } - default: - ereport(ERROR, - (errmsg_internal("unknown Cypher string match operation"))); + vle_expr = get_current_single_vle_path_expr(cpstate, linitial(fn->args)); + if (vle_expr == NULL) + { + return NULL; } - expr = transform_cypher_expr_recurse(cpstate, csm_node->lhs); - args = lappend(args, expr); - expr = transform_cypher_expr_recurse(cpstate, csm_node->rhs); - args = lappend(args, expr); + func_oid = get_age_materialize_vle_nodes_oid(); + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, list_make1(vle_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - func_expr = makeFuncExpr(func_access_oid, AGTYPEOID, args, InvalidOid, - InvalidOid, COERCE_EXPLICIT_CALL); - func_expr->location = csm_node->location; +static bool get_nonnegative_integer_const(Node *node, int64 *value) +{ + if (!is_ag_node(node, cypher_integer_const)) + { + return false; + } - return (Node *)func_expr; + *value = ((cypher_integer_const *)node)->integer; + return *value >= 0; } -/* - * Function to create a typecasting node - */ -static Node *transform_cypher_typecast(cypher_parsestate *cpstate, - cypher_typecast *ctypecast) +static bool parse_vle_path_nested_transform_index(cypher_parsestate *cpstate, + A_Indirection *a_ind, + Expr **vle_expr, + char **list_name, + Const **lower_expr, + Const **upper_expr, + int64 *mode) { - List *fname; - FuncCall *fnode; - ParseState *pstate; - TypeName *target_typ; + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *base_fn = NULL; + A_Indices *indices = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *base_name = NULL; + int64 index; + int64 mode_flag = 0; + + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return false; + } - /* verify input parameter */ - Assert (cpstate != NULL); - Assert (ctypecast != NULL); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &index)) + { + return false; + } - /* create the qualified function name, schema first */ - fname = list_make1(makeString("ag_catalog")); - pstate = &cpstate->pstate; - target_typ = ctypecast->typname; - - if (list_length(target_typ->names) == 1) + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) { - char *typecast = strVal(linitial(target_typ->names)); - int typecast_len = strlen(typecast); + return false; + } - /* append the name of the requested typecast function */ - if (typecast_len == 4 && pg_strcasecmp(typecast, "edge") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_EDGE)); - } - else if (typecast_len == 4 && pg_strcasecmp(typecast, "path") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PATH)); - } - else if (typecast_len == 6 && pg_strcasecmp(typecast, "vertex") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_VERTEX)); - } - else if (typecast_len == 7 && pg_strcasecmp(typecast, "numeric") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_NUMERIC)); - } - else if (typecast_len == 5 && pg_strcasecmp(typecast, "float") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_FLOAT)); - } - else if ((typecast_len == 3 && - pg_strcasecmp(typecast, "int") == 0) || - (typecast_len == 7 && - pg_strcasecmp(typecast, "integer") == 0)) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_INT)); - } - else if (typecast_len == 9 && - pg_strcasecmp(typecast, "pg_float8") == 0) - { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_FLOAT8)); - } - else if (typecast_len == 9 && - pg_strcasecmp(typecast, "pg_bigint") == 0) + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0) + { + return false; + } + + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) + { + return false; + } + + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 360; + } + else if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(inner_name, "reverse") == 0) + { + mode_flag = 240; + } + else if (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(inner_name, "tail") == 0) + { + mode_flag = 120; + } + else + { + return false; + } + + if (IsA(linitial(inner_fn->args), FuncCall)) + { + base_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_BIGINT)); + return false; } - else if ((typecast_len == 4 && - pg_strcasecmp(typecast, "bool") == 0) || - (typecast_len == 7 && - pg_strcasecmp(typecast, "boolean") == 0)) + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") == 0 || + pg_strcasecmp(base_name, "relationships") == 0) { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_BOOL)); + *list_name = base_name; + *vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + *mode = (pg_strcasecmp(base_name, "nodes") == 0 ? 6 : 4) + + mode_flag; } - else if (typecast_len == 7 && - pg_strcasecmp(typecast, "pg_text") == 0) + else { - fname = lappend(fname, makeString(FUNC_AGTYPE_TYPECAST_PG_TEXT)); + return false; } - else + } + else + { + *vle_expr = get_current_vle_edge_expr(cpstate, + linitial(inner_fn->args)); + if (*vle_expr != NULL) { - goto fallback_coercion; + *list_name = "relationships"; + *mode = 4 + mode_flag; } - - /* make a function call node */ - fnode = makeFuncCall(fname, list_make1(ctypecast->expr), COERCE_SQL_SYNTAX, - ctypecast->location); - - /* return the transformed function */ - return transform_FuncCall(cpstate, fnode); } -fallback_coercion: + if (*vle_expr == NULL) { - Oid source_oid; - Oid target_oid; - int32 t_typmod = -1; - Node *expr; + return false; + } - /* transform the expr before casting */ - expr = transform_cypher_expr_recurse(cpstate, - ctypecast->expr); + *lower_expr = make_agtype_integer_const(index, exprLocation(indices->uidx)); + *upper_expr = make_agtype_integer_const(index + 1, + exprLocation(indices->uidx)); - typenameTypeIdAndMod(pstate, target_typ, &target_oid, &t_typmod); - source_oid = exprType(expr); + return true; +} - /* errors out if cast not possible */ - expr = coerce_expr_flexible(pstate, expr, source_oid, target_oid, - t_typmod, true); +static Node *try_transform_vle_path_tail_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) +{ + FuncCall *tail_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + char *list_name = NULL; + int64 tail_index; - return expr; + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; } -} -/* - * Helper function to coerce an expression to the target type. If - * no direct cast exists, it attempts to cast through text if the - * source or target type is agtype. This improves interoperability - * with types from other extensions. - */ -static Node *coerce_expr_flexible(ParseState *pstate, Node *expr, - Oid source_oid, Oid target_oid, - int32 t_typmod, bool error_out) -{ - const Oid text_oid = TEXTOID; - Node *result; + tail_fn = (FuncCall *)a_ind->arg; + if (list_length(tail_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(tail_fn->funcname)), "tail") != 0 || + list_length(tail_fn->args) != 1) + { + return NULL; + } - if (expr == NULL) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &tail_index)) + { return NULL; + } - /* Try a direct cast */ - result = coerce_to_target_type(pstate, expr, source_oid, target_oid, - t_typmod, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (result != NULL) - return result; + if (IsA(linitial(tail_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(tail_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - /* Try cast via TEXT if either side is AGTYPE */ - if (source_oid == AGTYPEOID || target_oid == AGTYPEOID) + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + return NULL; + } + } + else { - Node *to_text = coerce_to_target_type(pstate, expr, source_oid, text_oid, - -1, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (to_text != NULL) + vle_expr = get_current_vle_edge_expr(cpstate, linitial(tail_fn->args)); + if (vle_expr != NULL) { - result = coerce_to_target_type(pstate, to_text, text_oid, target_oid, - t_typmod, COERCION_EXPLICIT, - COERCE_EXPLICIT_CAST, -1); - if (result != NULL) - return result; + list_name = "relationships"; } } - if (error_out) + if (vle_expr == NULL) { - ereport(ERROR, - (errmsg_internal("typecast \'%s\' not supported", - format_type_be(target_oid)))); + return NULL; } - return NULL; + index_expr = make_agtype_integer_const(tail_index + 1, + exprLocation(indices->uidx)); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_materialize_vle_node_at_oid(); + } + else + { + func_oid = get_age_materialize_vle_edge_at_oid(); + } + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Node *transform_external_ext_FuncCall(cypher_parsestate *cpstate, - FuncCall *fn, List *targs, - Form_pg_proc procform, - const char *extension) +static Node *try_transform_vle_path_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - ParseState *pstate = &cpstate->pstate; - FuncExpr *fexpr = NULL; - Node *retval = NULL; - Node *last_srf = pstate->p_last_srf; - Oid *proargtypes; + FuncCall *reverse_fn = NULL; + FuncCall *list_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + char *list_name = NULL; + int64 reverse_index; - /* make sure procform in not NULL */ - Assert(procform != NULL); - proargtypes = procform->proargtypes.values; + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - /* cast the agtype arguments to the types accepted by function */ - targs = cast_agtype_args_to_target_type(cpstate, procform, targs, proargtypes); + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) + { + return NULL; + } - /* now get the function node for the external function */ - fexpr = (FuncExpr *)ParseFuncOrColumn(pstate, fn->funcname, targs, - last_srf, fn, false, - fn->location); - pfree(procform); + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL || + !get_nonnegative_integer_const(indices->uidx, &reverse_index)) + { + return NULL; + } - /* - * This will cast TEXT output to AGTYPE. It will error out if this is - * not possible to do. For TEXT to AGTYPE we need to wrap the output - * due to issues with creating a cast from TEXT to AGTYPE. - */ - if (fexpr->funcresulttype == TEXTOID) + if (IsA(linitial(reverse_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(reverse_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } + + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") == 0 || + pg_strcasecmp(list_name, "relationships") == 0) + { + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + return NULL; + } + } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(reverse_fn->args)); + if (vle_expr != NULL) + { + list_name = "relationships"; + } + } + + if (vle_expr == NULL) + { + return NULL; + } + + index_expr = make_agtype_integer_const(-reverse_index - 1, + exprLocation(indices->uidx)); + if (pg_strcasecmp(list_name, "nodes") == 0) { - retval = wrap_text_output_to_agtype(cpstate, fexpr); + func_oid = get_age_materialize_vle_node_at_oid(); } else { - retval = (Node *)fexpr; + func_oid = get_age_materialize_vle_edge_at_oid(); } - /* additional casts or wraps can be done here for other types */ + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); +} - /* flag that an aggregate was found during a transform */ - if (retval != NULL && retval->type == T_Aggref) +static Node *try_transform_vle_path_nested_transform_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) +{ + Expr *vle_expr = NULL; + char *list_name = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + int64 mode; + + if (!parse_vle_path_nested_transform_index(cpstate, a_ind, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) { - cpstate->exprHasAgg = true; + return NULL; } - /* we can just return it here */ - return retval; + mode_expr = make_agtype_integer_const(mode, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Cast a function's input parameter list from agtype to that function's input - * type. This is used for functions that don't take agtype as input and where - * there isn't an implicit cast to do this for us. - */ -static List *cast_agtype_args_to_target_type(cypher_parsestate *cpstate, - Form_pg_proc procform, - List *fargs, - Oid *target_types) +static Node *try_transform_vle_path_nodes_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - char *funcname = NameStr(procform->proname); - int nargs = procform->pronargs; - int given_nargs = list_length(fargs); - ListCell *lc = NULL; + FuncCall *nodes_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; - /* verify the length of args are same */ - if (given_nargs != nargs) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("function %s requires %d arguments, %d given", - funcname, nargs, given_nargs))); + return NULL; } - /* iterate through the function's args */ - foreach (lc, fargs) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) { - Node *expr = lfirst(lc); - Oid source_oid = exprType(expr); - Oid target_oid = target_types[foreach_current_index(lc)]; - - if (source_oid == target_oid) - { - continue; - } - - /* errors out if cast not possible */ - expr = coerce_expr_flexible(&cpstate->pstate, expr, source_oid, - target_oid, -1, true); - - lfirst(lc) = expr; + return NULL; } - return fargs; -} - -/* - * Due to issues with creating a cast from text to agtype, we need to wrap a - * function that outputs text with text_to_agtype. - */ -static Node *wrap_text_output_to_agtype(cypher_parsestate *cpstate, - FuncExpr *fexpr) -{ - Oid func_oid; - FuncExpr *retval; + nodes_func = (FuncCall *)a_ind->arg; + if (list_length(nodes_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(nodes_func->funcname)), "nodes") != 0 || + list_length(nodes_func->args) != 1) + { + return NULL; + } - if (fexpr->funcresulttype != TEXTOID) + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(nodes_func->args)); + if (vle_expr == NULL) { - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), - errmsg("can only wrap text to agtype"))); + return NULL; } - func_oid = get_text_to_agtype_oid(); - retval = makeFuncExpr(func_oid, AGTYPEOID, list_make1(fexpr), InvalidOid, - InvalidOid, COERCE_SQL_SYNTAX); - retval->location = fexpr->location; + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_node_at_oid(); - /* return the wrapped function */ - return (Node *)retval; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -/* - * Returns Form_pg_proc struct for given function, if the function - * is not in search path, it is not considered. - */ -static Form_pg_proc get_procform(FuncCall *fn, bool err_not_found) +static Node *try_transform_vle_path_nodes_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - CatCList *catlist = NULL; - Form_pg_proc procform = NULL; - Form_pg_proc result = NULL; - int nargs; - int i = 0; - List *asp = NIL; - bool asp_fetched = false; - bool found = false; - char *funcname = (((String*)linitial(fn->funcname))->sval); - int funcname_len = strlen(funcname); - - /* get a list of matching functions */ - catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); + FuncCall *nodes_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Oid func_oid; - if (catlist->n_members == 0) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - ReleaseSysCacheList(catlist); return NULL; } - nargs = list_length(fn->args); - - /* iterate through them and verify that they are in the search path */ - for (i = 0; i < catlist->n_members; i++) + indices = linitial(a_ind->indirection); + if (!indices->is_slice) { - HeapTuple proctup = &catlist->members[i]->tuple; - procform = (Form_pg_proc) GETSTRUCT(proctup); - - /* - * Check if the function name, number of arguments, and - * variadic match before checking if it is in the search - * path. - */ - if (nargs == procform->pronargs && - fn->func_variadic == procform->provariadic && - strnlen(NameStr(procform->proname), NAMEDATALEN) == funcname_len && - pg_strcasecmp(funcname, procform->proname.data) == 0) - { - if (!asp_fetched) - { - asp = fetch_search_path(false); - asp_fetched = true; - } + return NULL; + } - if (list_member_oid(asp, procform->pronamespace) && - !isTempNamespace(procform->pronamespace)) - { - found = true; - } - } + nodes_func = (FuncCall *)a_ind->arg; + if (list_length(nodes_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(nodes_func->funcname)), "nodes") != 0 || + list_length(nodes_func->args) != 1) + { + return NULL; + } - if (found) - { - result = palloc(sizeof(FormData_pg_proc)); - memcpy(result, procform, sizeof(FormData_pg_proc)); - break; - } + vle_expr = get_current_single_vle_path_expr(cpstate, + linitial(nodes_func->args)); + if (vle_expr == NULL) + { + return NULL; + } - /* reset procform */ - procform = NULL; + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); } - /* Error out if function not found */ - if (err_not_found && (result == NULL)) + if (indices->uidx == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_FUNCTION), - errmsg("function %s does not exist", funcname), - errhint("If the function is from an external extension, " - "make sure the extension is installed and the " - "function is in the search path."))); + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); } - /* we need to release the cache list */ - ReleaseSysCacheList(catlist); - list_free(asp); + func_oid = get_age_materialize_vle_nodes_slice_oid(); - return result; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make3(vle_expr, lower_expr, upper_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static const char *get_mapped_extension(Oid func_oid) +static Node *try_transform_vle_path_list_slice(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - Oid extension_oid; - char *extension = NULL; - function_extension_cache_entry *entry; - bool found; + A_Indices *indices = NULL; + FuncCall *outer_fn = NULL; + FuncCall *list_fn = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; + char *outer_name = NULL; + char *list_name = NULL; + int64 mode = 0; - initialize_function_extension_cache(); + if (list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) + { + return NULL; + } - entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); - if (entry != NULL) + indices = linitial(a_ind->indirection); + if (!indices->is_slice) { - if (!entry->has_extension) + return NULL; + } + + if (!IsA(a_ind->arg, FuncCall)) + { + vle_expr = get_current_vle_edge_expr(cpstate, a_ind->arg); + if (vle_expr == NULL) { return NULL; } - return NameStr(entry->extension); + + mode = 0; + goto build_slice; } - extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); - extension = get_extension_name(extension_oid); + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1) + { + return NULL; + } - if (function_extension_cache == NULL) + outer_name = strVal(linitial(outer_fn->funcname)); + if ((pg_strcasecmp(outer_name, "tail") != 0 && + pg_strcasecmp(outer_name, "reverse") != 0)) { - initialize_function_extension_cache(); + return NULL; } - entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, - &found); - if (!found) + if (IsA(linitial(outer_fn->args), FuncCall)) { - entry->has_extension = (extension != NULL); - if (entry->has_extension) + list_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) { - namestrcpy(&entry->extension, extension); + return NULL; } - } - if (extension != NULL) - { - pfree(extension); - } + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "tail") == 0) + { + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; - return entry->has_extension ? NameStr(entry->extension) : NULL; -} + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } -static bool function_belongs_to_extension(Oid func_oid, const char *extension) -{ - Oid extension_oid; - char *extension_name = NULL; - function_extension_cache_entry *entry; - bool belongs; - bool found; + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } - initialize_function_extension_cache(); + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + mode = pg_strcasecmp(base_name, "nodes") == 0 ? 7 : 6; + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = 6; + } - entry = hash_search(function_extension_cache, &func_oid, HASH_FIND, NULL); - if (entry != NULL) - { - return entry->has_extension && - pg_strcasecmp(NameStr(entry->extension), extension) == 0; + goto check_vle_expr; + } + + if ((pg_strcasecmp(outer_name, "tail") == 0 && + pg_strcasecmp(list_name, "reverse") == 0) || + (pg_strcasecmp(outer_name, "reverse") == 0 && + pg_strcasecmp(list_name, "tail") == 0)) + { + bool tail_reverse = pg_strcasecmp(outer_name, "tail") == 0; + + if (IsA(linitial(list_fn->args), FuncCall)) + { + FuncCall *base_fn = (FuncCall *)linitial(list_fn->args); + char *base_name = NULL; + bool node_list = false; + + if (list_length(base_fn->funcname) != 1 || + list_length(base_fn->args) != 1) + { + return NULL; + } + + base_name = strVal(linitial(base_fn->funcname)); + if (pg_strcasecmp(base_name, "nodes") != 0 && + pg_strcasecmp(base_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(base_fn->args)); + node_list = pg_strcasecmp(base_name, "nodes") == 0; + mode = tail_reverse ? (node_list ? 9 : 8) : + (node_list ? 11 : 10); + } + else + { + vle_expr = get_current_vle_edge_expr( + cpstate, linitial(list_fn->args)); + mode = tail_reverse ? 8 : 10; + } + + goto check_vle_expr; + } + + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) + { + return NULL; + } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + if (pg_strcasecmp(outer_name, "tail") == 0) + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 3 : 2; + } + else + { + mode = pg_strcasecmp(list_name, "nodes") == 0 ? 5 : 4; + } } + else + { + vle_expr = get_current_vle_edge_expr(cpstate, + linitial(outer_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - extension_oid = getExtensionOfObject(ProcedureRelationId, func_oid); - extension_name = get_extension_name(extension_oid); + mode = pg_strcasecmp(outer_name, "tail") == 0 ? 2 : 4; + } - if (function_extension_cache == NULL) +check_vle_expr: + if (vle_expr == NULL) { - initialize_function_extension_cache(); + return NULL; } - entry = hash_search(function_extension_cache, &func_oid, HASH_ENTER, - &found); - if (!found) +build_slice: + if (indices->lidx == NULL) { - entry->has_extension = (extension_name != NULL); - if (entry->has_extension) - { - namestrcpy(&entry->extension, extension_name); - } + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); } - - belongs = (extension_name != NULL && - pg_strcasecmp(extension_name, extension) == 0); - - if (extension_name != NULL) + else { - pfree(extension_name); + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); } - return belongs; -} - -static bool is_extension_external(const char *extension) -{ - return ((extension != NULL) && - (pg_strcasecmp(extension, "age") != 0)); -} - -static bool function_needs_graph_name_argument(const char *name, int name_len) -{ - switch (name[0]) + if (indices->uidx == NULL) { - case 'e': - return name_len == 7 && memcmp(name, "endNode", 7) == 0; - case 's': - return name_len == 9 && memcmp(name, "startNode", 9) == 0; - case 'v': - return (name_len == 3 && memcmp(name, "vle", 3) == 0) || - (name_len == 12 && - memcmp(name, "vertex_stats", 12) == 0); - default: - return false; + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); } -} - -/* Returns age_ prefiexed lower case function name */ -static char *construct_age_function_name(char *funcname, int funcname_len) -{ - char *ag_name = palloc(funcname_len + 5); - int i; - - /* copy in the prefix - all AGE functions are prefixed with age_ */ - memcpy(ag_name, "age_", 4); - - /* - * All AGE function names are in lower case. So, copy in the funcname - * in lower case. - */ - for (i = 0; i < funcname_len; i++) + else { - ag_name[i + 4] = tolower(funcname[i]); + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); } - /* terminate it with 0 */ - ag_name[i + 4] = 0; + mode_expr = make_agtype_integer_const(mode, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_list_slice_oid(); - return ag_name; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } - -/* - * Checks if a function exists. If the extension name is given, - * then it checks if the function exists in that extension. - */ -static bool function_exists(char *funcname, char *extension) +static Node *try_transform_vle_path_boundary_id_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - CatCList *catlist = NULL; - bool found = false; - function_exists_cache_key key; - function_exists_cache_entry *entry; - bool cache_found; - int i = 0; + FuncCall *outer_fn = NULL; + FuncCall *inner_fn = NULL; + FuncCall *list_fn = NULL; + char *outer_name = NULL; + char *inner_name = NULL; + char *list_name = NULL; + Expr *vle_expr = NULL; + Const *index_expr = NULL; + Oid func_oid; + int64 index; - initialize_function_exists_cache(); - MemSet(&key, 0, sizeof(key)); - namestrcpy(&key.funcname, funcname); - if (extension != NULL) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), String) || + pg_strcasecmp(strVal(linitial(a_ind->indirection)), "id") != 0) { - namestrcpy(&key.extension, extension); + return NULL; } - entry = hash_search(function_exists_cache, &key, HASH_FIND, NULL); - if (entry != NULL) + outer_fn = (FuncCall *)a_ind->arg; + if (list_length(outer_fn->funcname) != 1 || + list_length(outer_fn->args) != 1 || + !IsA(linitial(outer_fn->args), FuncCall)) { - return entry->exists; + return NULL; } - /* get a list of matching functions */ - catlist = SearchSysCacheList1(PROCNAMEARGSNSP, CStringGetDatum(funcname)); - - if (catlist->n_members == 0) + outer_name = strVal(linitial(outer_fn->funcname)); + if (pg_strcasecmp(outer_name, "head") != 0 && + pg_strcasecmp(outer_name, "last") != 0) { - ReleaseSysCacheList(catlist); - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - entry->exists = false; - return false; + return NULL; } - else if (extension == NULL) + + inner_fn = (FuncCall *)linitial(outer_fn->args); + if (list_length(inner_fn->funcname) != 1 || + list_length(inner_fn->args) != 1) { - ReleaseSysCacheList(catlist); - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - entry->exists = true; - return true; + return NULL; } - for (i = 0; i < catlist->n_members; i++) + inner_name = strVal(linitial(inner_fn->funcname)); + if (pg_strcasecmp(inner_name, "nodes") == 0 || + pg_strcasecmp(inner_name, "relationships") == 0) { - HeapTuple proctup = &catlist->members[i]->tuple; - Form_pg_proc procform = (Form_pg_proc) GETSTRUCT(proctup); + list_name = inner_name; + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(inner_fn->args)); + index = pg_strcasecmp(outer_name, "head") == 0 ? 0 : -1; + } + else if ((pg_strcasecmp(inner_name, "tail") == 0 || + pg_strcasecmp(inner_name, "reverse") == 0) && + IsA(linitial(inner_fn->args), FuncCall)) + { + list_fn = (FuncCall *)linitial(inner_fn->args); + if (list_length(list_fn->funcname) != 1 || + list_length(list_fn->args) != 1) + { + return NULL; + } - if (function_belongs_to_extension(procform->oid, extension)) + list_name = strVal(linitial(list_fn->funcname)); + if (pg_strcasecmp(list_name, "nodes") != 0 && + pg_strcasecmp(list_name, "relationships") != 0) { - found = true; - break; + return NULL; + } + + if (pg_strcasecmp(inner_name, "tail") == 0) + { + if (pg_strcasecmp(outer_name, "head") != 0) + { + return NULL; + } + index = 1; + } + else + { + index = pg_strcasecmp(outer_name, "head") == 0 ? -1 : 0; } + + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(list_fn->args)); + } + else + { + return NULL; } - /* we need to release the cache list */ - ReleaseSysCacheList(catlist); + if (vle_expr == NULL) + { + return NULL; + } - entry = hash_search(function_exists_cache, &key, HASH_ENTER, - &cache_found); - entry->exists = found; + index_expr = make_agtype_integer_const(index, outer_fn->location); + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_id_at_oid(); + } + else + { + func_oid = get_age_vle_edge_id_at_oid(); + } - return found; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static void initialize_function_exists_cache(void) +static FuncExpr *make_vle_index_properties_expr(cypher_parsestate *cpstate, + A_Indirection *indexed_arg, + int location) { - HASHCTL hash_ctl; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *mode_expr = NULL; + char *list_name = NULL; + Oid func_oid; + int64 mode; - if (function_exists_cache != NULL) + if (list_length(indexed_arg->indirection) != 1 || + !IsA(linitial(indexed_arg->indirection), A_Indices)) { - return; + return NULL; } - initialize_function_caches(); - - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(function_exists_cache_key); - hash_ctl.entrysize = sizeof(function_exists_cache_entry); - hash_ctl.hcxt = CacheMemoryContext; - - function_exists_cache = hash_create("cypher function existence cache", 16, - &hash_ctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); -} + indices = linitial(indexed_arg->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } -static void initialize_function_extension_cache(void) -{ - HASHCTL hash_ctl; + if (parse_vle_path_nested_transform_index(cpstate, indexed_arg, &vle_expr, + &list_name, &lower_expr, + &upper_expr, &mode)) + { + mode_expr = make_agtype_integer_const(mode + 32, location); + func_oid = get_age_materialize_vle_slice_boundary_oid(); + return makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + } - if (function_extension_cache != NULL) + vle_expr = get_current_vle_edge_expr(cpstate, indexed_arg->arg); + if (vle_expr != NULL) { - return; + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_vle_edge_properties_at_oid(); + return makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } - initialize_function_caches(); + if (!parse_vle_path_indexed_list_index(cpstate, indexed_arg, &vle_expr, + &list_name, &index_expr)) + { + return NULL; + } - MemSet(&hash_ctl, 0, sizeof(hash_ctl)); - hash_ctl.keysize = sizeof(Oid); - hash_ctl.entrysize = sizeof(function_extension_cache_entry); - hash_ctl.hcxt = CacheMemoryContext; + if (pg_strcasecmp(list_name, "nodes") == 0) + { + func_oid = get_age_vle_node_properties_at_oid(); + } + else + { + func_oid = get_age_vle_edge_properties_at_oid(); + } - function_extension_cache = - hash_create("cypher function extension cache", 16, &hash_ctl, - HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + return makeFuncExpr(func_oid, AGTYPEOID, list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); } -static void initialize_function_caches(void) +static Node *try_transform_vle_path_boundary_property_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - if (!CacheMemoryContext) + FuncExpr *properties_expr = NULL; + FuncExpr *access_expr = NULL; + List *args = NIL; + ListCell *lc = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Const *mode_expr = NULL; + Oid properties_func_oid; + Oid access_func_oid; + char *list_name = NULL; + int64 index; + bool start_endpoint = false; + bool endpoint_index = false; + bool tail_last = false; + bool tail_last_endpoint = false; + bool skip_first_indirection = false; + + if (list_length(a_ind->indirection) < 1) { - CreateCacheMemoryContext(); + return NULL; } - if (!function_cache_callback_registered) + if (IsA(a_ind->arg, A_Indirection)) { - CacheRegisterSyscacheCallback(PROCOID, invalidate_function_caches, - (Datum)0); - CacheRegisterSyscacheCallback(PROCNAMEARGSNSP, - invalidate_function_caches, - (Datum)0); - function_cache_callback_registered = true; + A_Indirection *nested_arg = (A_Indirection *)a_ind->arg; + + properties_expr = make_vle_index_properties_expr( + cpstate, nested_arg, exprLocation((Node *)nested_arg)); + if (properties_expr != NULL) + { + goto build_access; + } + + return NULL; } -} -static void invalidate_function_caches(Datum arg, int cache_id, - uint32 hash_value) -{ - if (function_exists_cache != NULL) + if (list_length(a_ind->indirection) > 1 && + IsA(linitial(a_ind->indirection), A_Indices)) { - hash_destroy(function_exists_cache); - function_exists_cache = NULL; + A_Indirection indexed_arg; + + MemSet(&indexed_arg, 0, sizeof(A_Indirection)); + indexed_arg.type = T_A_Indirection; + indexed_arg.arg = a_ind->arg; + indexed_arg.indirection = list_make1(linitial(a_ind->indirection)); + + properties_expr = make_vle_index_properties_expr( + cpstate, &indexed_arg, exprLocation((Node *)a_ind)); + if (properties_expr != NULL) + { + skip_first_indirection = true; + goto build_access; + } } - if (function_extension_cache != NULL) + if (!IsA(a_ind->arg, FuncCall)) { - hash_destroy(function_extension_cache); - function_extension_cache = NULL; + return NULL; } - agtype_access_operator_oid = InvalidOid; - agtype_access_slice_oid = InvalidOid; - agtype_in_operator_oid = InvalidOid; - agtype_add_oid = InvalidOid; - agtype_build_empty_list_oid = InvalidOid; - agtype_build_list_oid = InvalidOid; - agtype_build_empty_map_oid = InvalidOid; - agtype_build_map_oid = InvalidOid; - agtype_build_map_nonull_oid = InvalidOid; - age_properties_oid = InvalidOid; - agtype_string_match_starts_with_oid = InvalidOid; - agtype_string_match_ends_with_oid = InvalidOid; - agtype_string_match_contains_oid = InvalidOid; - text_to_agtype_oid = InvalidOid; -} + if (IsA(a_ind->arg, FuncCall)) + { + FuncCall *arg_fn = (FuncCall *)a_ind->arg; -static Oid get_agtype_access_operator_oid(void) -{ - initialize_function_caches(); + if (list_length(arg_fn->funcname) == 1 && + list_length(arg_fn->args) == 1 && + (IsA(linitial(arg_fn->args), FuncCall) || + IsA(linitial(arg_fn->args), A_Indirection))) + { + char *endpoint_name = strVal(linitial(arg_fn->funcname)); + + if (pg_strcasecmp(endpoint_name, "startNode") == 0 || + pg_strcasecmp(endpoint_name, "endNode") == 0) + { + int64 mode_offset = + pg_strcasecmp(endpoint_name, "startNode") == 0 ? 104 : 112; + + if (IsA(linitial(arg_fn->args), FuncCall)) + { + properties_expr = + (FuncExpr *)transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)linitial(arg_fn->args), + mode_offset); + if (properties_expr != NULL) + { + goto build_access; + } + + properties_expr = + (FuncExpr *)transform_vle_path_slice_head_last( + cpstate, (FuncCall *)linitial(arg_fn->args), + mode_offset); + if (properties_expr != NULL) + { + goto build_access; + } + } + else + { + A_Indirection *endpoint_arg = linitial(arg_fn->args); + Const *lower_expr = NULL; + Const *upper_expr = NULL; + Const *nested_mode_expr = NULL; + int64 nested_mode; + + if (parse_vle_path_nested_transform_index( + cpstate, endpoint_arg, &vle_expr, &list_name, + &lower_expr, &upper_expr, &nested_mode) && + pg_strcasecmp(list_name, "relationships") == 0) + { + nested_mode_expr = make_agtype_integer_const( + nested_mode + mode_offset, + exprLocation((Node *)endpoint_arg)); + properties_func_oid = + get_age_materialize_vle_slice_boundary_oid(); + properties_expr = makeFuncExpr( + properties_func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + nested_mode_expr), + InvalidOid, InvalidOid, COERCE_EXPLICIT_CALL); + goto build_access; + } + } + } + } + } - if (!OidIsValid(agtype_access_operator_oid)) + properties_expr = + (FuncExpr *)transform_vle_path_nested_transform_head_last( + cpstate, (FuncCall *)a_ind->arg, 32); + if (properties_expr != NULL) { - agtype_access_operator_oid = - get_ag_func_oid("agtype_access_operator", 1, AGTYPEARRAYOID); + goto build_access; } - return agtype_access_operator_oid; -} - -static Oid get_agtype_access_slice_oid(void) -{ - initialize_function_caches(); - - if (!OidIsValid(agtype_access_slice_oid)) + properties_expr = (FuncExpr *)transform_vle_path_slice_head_last( + cpstate, (FuncCall *)a_ind->arg, 32); + if (properties_expr != NULL) { - agtype_access_slice_oid = - get_ag_func_oid("agtype_access_slice", 3, AGTYPEOID, AGTYPEOID, - AGTYPEOID); + goto build_access; } - return agtype_access_slice_oid; -} - -static Oid get_agtype_in_operator_oid(void) -{ - initialize_function_caches(); - - if (!OidIsValid(agtype_in_operator_oid)) + tail_last_endpoint = parse_vle_tail_last_endpoint(cpstate, a_ind->arg, + &vle_expr, + &start_endpoint); + if (!tail_last_endpoint) { - agtype_in_operator_oid = - get_ag_func_oid("agtype_in_operator", 2, AGTYPEOID, AGTYPEOID); + endpoint_index = parse_vle_edge_endpoint_index(cpstate, a_ind->arg, + &vle_expr, &index_expr, + &start_endpoint); } - - return agtype_in_operator_oid; -} - -static Oid get_agtype_add_oid(void) -{ - initialize_function_caches(); - - if (!OidIsValid(agtype_add_oid)) + if (!tail_last_endpoint && !endpoint_index) { - agtype_add_oid = - get_ag_func_oid("agtype_add", 2, AGTYPEOID, AGTYPEOID); + tail_last = parse_vle_path_tail_last_list(cpstate, a_ind->arg, + &vle_expr, &list_name); + } + if (!tail_last_endpoint && !endpoint_index && !tail_last && + !parse_vle_path_boundary_list_index(cpstate, a_ind->arg, &vle_expr, + &list_name, &index)) + { + return NULL; } - return agtype_add_oid; -} + if (tail_last_endpoint) + { + int64 mode = start_endpoint ? 4 : 5; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_tail_last_endpoint_field_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (endpoint_index) + { + int64 mode = start_endpoint ? 4 : 5; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_edge_endpoint_field_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make3(vle_expr, index_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (tail_last) + { + int64 mode = pg_strcasecmp(list_name, "nodes") == 0 ? 2 : 4; + + mode_expr = make_agtype_integer_const(mode, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_tail_last_field_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else if (pg_strcasecmp(list_name, "nodes") == 0) + { + index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_node_properties_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } + else + { + index_expr = (Node *)make_agtype_integer_const( + index, exprLocation(a_ind->arg)); + properties_func_oid = get_age_vle_edge_properties_at_oid(); + properties_expr = makeFuncExpr(properties_func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + } -static Oid get_agtype_build_empty_list_oid(void) -{ - initialize_function_caches(); +build_access: + properties_expr->location = exprLocation(a_ind->arg); - if (!OidIsValid(agtype_build_empty_list_oid)) + args = lappend(args, properties_expr); + foreach (lc, a_ind->indirection) { - agtype_build_empty_list_oid = - get_ag_func_oid("agtype_build_list", 0); - } + Node *node = lfirst(lc); - return agtype_build_empty_list_oid; -} + if (skip_first_indirection) + { + skip_first_indirection = false; + continue; + } -static Oid get_agtype_build_list_oid(void) -{ - initialize_function_caches(); + if (IsA(node, String)) + { + Const *const_str = makeConst(AGTYPEOID, -1, InvalidOid, -1, + string_to_agtype(strVal(node)), + false, false); - if (!OidIsValid(agtype_build_list_oid)) - { - agtype_build_list_oid = - get_ag_func_oid("agtype_build_list", 1, ANYOID); + args = lappend(args, const_str); + } + else if (IsA(node, A_Indices)) + { + A_Indices *indices = (A_Indices *)node; + + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } + args = lappend(args, + transform_cypher_expr_recurse(cpstate, + indices->uidx)); + } + else + { + return NULL; + } } - return agtype_build_list_oid; + access_func_oid = get_agtype_access_operator_oid(); + access_expr = makeFuncExpr(access_func_oid, AGTYPEOID, + list_make1(make_agtype_array_expr(args)), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); + access_expr->funcvariadic = true; + access_expr->location = exprLocation(a_ind->arg); + + return (Node *)access_expr; } -static Oid get_agtype_build_empty_map_oid(void) +static Node *try_transform_vle_path_relationships_access( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - initialize_function_caches(); + FuncCall *relationships_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; - if (!OidIsValid(agtype_build_empty_map_oid)) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - agtype_build_empty_map_oid = get_ag_func_oid("agtype_build_map", 0); + return NULL; } - return agtype_build_empty_map_oid; -} + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) + { + return NULL; + } -static Oid get_agtype_build_map_oid(void) -{ - initialize_function_caches(); + relationships_func = (FuncCall *)a_ind->arg; + if (list_length(relationships_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(relationships_func->funcname)), + "relationships") != 0 || + list_length(relationships_func->args) != 1) + { + return NULL; + } - if (!OidIsValid(agtype_build_map_oid)) + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(relationships_func->args)); + if (vle_expr == NULL) { - agtype_build_map_oid = - get_ag_func_oid("agtype_build_map", 1, ANYOID); + return NULL; } - return agtype_build_map_oid; + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_edge_at_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Oid get_agtype_build_map_nonull_oid(void) +static Node *try_transform_vle_edge_reverse_access(cypher_parsestate *cpstate, + A_Indirection *a_ind) { - initialize_function_caches(); + FuncCall *reverse_fn = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *index_expr = NULL; + Oid func_oid; + int64 reverse_index; - if (!OidIsValid(agtype_build_map_nonull_oid)) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - agtype_build_map_nonull_oid = - get_ag_func_oid("agtype_build_map_nonull", 1, ANYOID); + return NULL; } - return agtype_build_map_nonull_oid; -} - -static Oid get_age_properties_oid(void) -{ - initialize_function_caches(); - - if (!OidIsValid(age_properties_oid)) + indices = linitial(a_ind->indirection); + if (indices->is_slice || indices->uidx == NULL) { - age_properties_oid = get_ag_func_oid("age_properties", 1, AGTYPEOID); + return NULL; } - return age_properties_oid; -} + reverse_fn = (FuncCall *)a_ind->arg; + if (list_length(reverse_fn->funcname) != 1 || + pg_strcasecmp(strVal(linitial(reverse_fn->funcname)), "reverse") != 0 || + list_length(reverse_fn->args) != 1) + { + return NULL; + } -static Oid get_agtype_string_match_starts_with_oid(void) -{ - initialize_function_caches(); + vle_expr = get_current_vle_edge_expr(cpstate, linitial(reverse_fn->args)); + if (vle_expr == NULL) + { + return NULL; + } - if (!OidIsValid(agtype_string_match_starts_with_oid)) + if (get_nonnegative_integer_const(indices->uidx, &reverse_index)) { - agtype_string_match_starts_with_oid = - get_ag_func_oid("agtype_string_match_starts_with", 2, AGTYPEOID, - AGTYPEOID); + index_expr = (Node *)make_agtype_integer_const( + -reverse_index - 1, exprLocation(indices->uidx)); + func_oid = get_age_materialize_vle_edge_at_oid(); + } + else + { + index_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); + func_oid = get_age_materialize_vle_edge_reversed_at_oid(); } - return agtype_string_match_starts_with_oid; + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make2(vle_expr, index_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } -static Oid get_agtype_string_match_ends_with_oid(void) +static Node *try_transform_vle_path_relationships_slice( + cypher_parsestate *cpstate, A_Indirection *a_ind) { - initialize_function_caches(); + FuncCall *relationships_func = NULL; + A_Indices *indices = NULL; + Expr *vle_expr = NULL; + Node *lower_expr = NULL; + Node *upper_expr = NULL; + Const *mode_expr = NULL; + Oid func_oid; - if (!OidIsValid(agtype_string_match_ends_with_oid)) + if (!IsA(a_ind->arg, FuncCall) || + list_length(a_ind->indirection) != 1 || + !IsA(linitial(a_ind->indirection), A_Indices)) { - agtype_string_match_ends_with_oid = - get_ag_func_oid("agtype_string_match_ends_with", 2, AGTYPEOID, - AGTYPEOID); + return NULL; } - return agtype_string_match_ends_with_oid; -} - -static Oid get_agtype_string_match_contains_oid(void) -{ - initialize_function_caches(); + indices = linitial(a_ind->indirection); + if (!indices->is_slice) + { + return NULL; + } - if (!OidIsValid(agtype_string_match_contains_oid)) + relationships_func = (FuncCall *)a_ind->arg; + if (list_length(relationships_func->funcname) != 1 || + pg_strcasecmp(strVal(linitial(relationships_func->funcname)), + "relationships") != 0 || + list_length(relationships_func->args) != 1) { - agtype_string_match_contains_oid = - get_ag_func_oid("agtype_string_match_contains", 2, AGTYPEOID, - AGTYPEOID); + return NULL; } - return agtype_string_match_contains_oid; -} + vle_expr = get_current_single_vle_path_expr( + cpstate, linitial(relationships_func->args)); + if (vle_expr == NULL) + { + return NULL; + } -static Oid get_text_to_agtype_oid(void) -{ - initialize_function_caches(); + if (indices->lidx == NULL) + { + lower_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + lower_expr = transform_cypher_expr_recurse(cpstate, indices->lidx); + } - if (!OidIsValid(text_to_agtype_oid)) + if (indices->uidx == NULL) { - text_to_agtype_oid = get_ag_func_oid("text_to_agtype", 1, TEXTOID); + upper_expr = (Node *)makeNullConst(AGTYPEOID, -1, InvalidOid); + } + else + { + upper_expr = transform_cypher_expr_recurse(cpstate, indices->uidx); } - return text_to_agtype_oid; + mode_expr = make_agtype_integer_const(0, exprLocation((Node *)a_ind)); + func_oid = get_age_materialize_vle_list_slice_oid(); + + return (Node *)makeFuncExpr(func_oid, AGTYPEOID, + list_make4(vle_expr, lower_expr, upper_expr, + mode_expr), + InvalidOid, InvalidOid, + COERCE_EXPLICIT_CALL); } /* @@ -2463,6 +8424,182 @@ static Node *transform_FuncCall(cypher_parsestate *cpstate, FuncCall *fn) ListCell *arg; Node *retval = NULL; + retval = try_transform_vle_path_endpoint_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_id_access( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_endpoint_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_id_access(cpstate, + fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_edge_variable_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_field( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_tail_last_endpoint_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_node_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_field(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_edge_label(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_edge_properties(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_endpoint_access( + cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_endpoint_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_boundary_id_function(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_index_id_function(cpstate, + fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_boundary_id_function(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_id_access(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_length(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_size(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_size(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_is_empty(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_is_empty(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_slice_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nested_transform_head_last(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_list_tail_reverse(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_relationships(cpstate, fn); + if (retval != NULL) + { + return retval; + } + retval = try_transform_vle_path_nodes(cpstate, fn); + if (retval != NULL) + { + return retval; + } + /* Transform the list of arguments ... */ foreach(arg, fn->args) { diff --git a/src/backend/utils/adt/age_global_graph.c b/src/backend/utils/adt/age_global_graph.c index bca608b9c..956d2a39b 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -109,6 +109,13 @@ static GraphVersionState *shmem_version_state = NULL; /* internal data structures implementation */ +typedef struct edge_label_adj_entry +{ + Oid edge_label_table_oid; + ListGraphId *edges; + struct edge_label_adj_entry *next; +} edge_label_adj_entry; + /* vertex entry for the vertex_hashtable */ typedef struct vertex_entry { @@ -116,6 +123,9 @@ typedef struct vertex_entry ListGraphId *edges_in; /* List of entering edges graphids (int64) */ ListGraphId *edges_out; /* List of exiting edges graphids (int64) */ ListGraphId *edges_self; /* List of selfloop edges graphids (int64) */ + edge_label_adj_entry *edges_in_by_label; + edge_label_adj_entry *edges_out_by_label; + edge_label_adj_entry *edges_self_by_label; Oid vertex_label_table_oid; /* the label table oid */ char *vertex_label_name; /* the label name */ ItemPointerData tid; /* physical tuple location for lazy fetch */ @@ -219,7 +229,14 @@ static bool insert_edge_entry(GRAPH_global_context *ggctx, graphid edge_id, const char *edge_label_name); static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, const char *edge_label_name); + graphid edge_id, Oid edge_label_table_oid, + const char *edge_label_name); +static void append_labeled_vertex_edge(edge_label_adj_entry **head, + Oid edge_label_table_oid, + graphid edge_id); +static ListGraphId *get_labeled_vertex_edges(edge_label_adj_entry *head, + Oid edge_label_table_oid); +static void free_labeled_vertex_edges(edge_label_adj_entry *head); static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, Oid vertex_label_table_oid, const char *vertex_label_name, @@ -554,6 +571,9 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, ve->edges_in = NULL; ve->edges_out = NULL; ve->edges_self = NULL; + ve->edges_in_by_label = NULL; + ve->edges_out_by_label = NULL; + ve->edges_self_by_label = NULL; /* we also need to store the vertex id for clean up of vertex lists */ ggctx->vertices = append_graphid(ggctx->vertices, vertex_id); @@ -570,7 +590,8 @@ static bool insert_vertex_entry(GRAPH_global_context *ggctx, graphid vertex_id, */ static bool insert_vertex_edge(GRAPH_global_context *ggctx, graphid start_vertex_id, graphid end_vertex_id, - graphid edge_id, const char *edge_label_name) + graphid edge_id, Oid edge_label_table_oid, + const char *edge_label_name) { vertex_entry *value = NULL; bool start_found = false; @@ -592,6 +613,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, if (start_found && is_selfloop) { value->edges_self = append_graphid(value->edges_self, edge_id); + append_labeled_vertex_edge(&value->edges_self_by_label, + edge_label_table_oid, edge_id); return true; } /* @@ -601,6 +624,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, else if (start_found) { value->edges_out = append_graphid(value->edges_out, edge_id); + append_labeled_vertex_edge(&value->edges_out_by_label, + edge_label_table_oid, edge_id); } /* search for the end vertex of the edge */ @@ -615,6 +640,8 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, if (start_found && end_found) { value->edges_in = append_graphid(value->edges_in, edge_id); + append_labeled_vertex_edge(&value->edges_in_by_label, + edge_label_table_oid, edge_id); return true; } /* @@ -649,6 +676,58 @@ static bool insert_vertex_edge(GRAPH_global_context *ggctx, return false; } +static void append_labeled_vertex_edge(edge_label_adj_entry **head, + Oid edge_label_table_oid, + graphid edge_id) +{ + edge_label_adj_entry *entry = NULL; + + for (entry = *head; entry != NULL; entry = entry->next) + { + if (entry->edge_label_table_oid == edge_label_table_oid) + { + entry->edges = append_graphid(entry->edges, edge_id); + return; + } + } + + entry = palloc0(sizeof(*entry)); + entry->edge_label_table_oid = edge_label_table_oid; + entry->edges = append_graphid(NULL, edge_id); + entry->next = *head; + *head = entry; +} + +static ListGraphId *get_labeled_vertex_edges(edge_label_adj_entry *head, + Oid edge_label_table_oid) +{ + edge_label_adj_entry *entry = NULL; + + for (entry = head; entry != NULL; entry = entry->next) + { + if (entry->edge_label_table_oid == edge_label_table_oid) + { + return entry->edges; + } + } + + return NULL; +} + +static void free_labeled_vertex_edges(edge_label_adj_entry *head) +{ + edge_label_adj_entry *entry = head; + + while (entry != NULL) + { + edge_label_adj_entry *next = entry->next; + + free_ListGraphId(entry->edges); + pfree(entry); + entry = next; + } +} + /* helper routine to load all vertices into the GRAPH global vertex hashtable */ static void load_vertex_hashtable(GRAPH_global_context *ggctx, Snapshot snapshot, @@ -840,6 +919,7 @@ static void load_edge_hashtable(GRAPH_global_context *ggctx, /* insert the edge into the start and end vertices edge lists */ inserted = insert_vertex_edge(ggctx, edge_vertex_start_id, edge_vertex_end_id, edge_id, + edge_label_table_oid, edge_label_name); if (!inserted) { @@ -916,10 +996,16 @@ static bool free_specific_GRAPH_global_context(GRAPH_global_context *ggctx) free_ListGraphId(value->edges_in); free_ListGraphId(value->edges_out); free_ListGraphId(value->edges_self); + free_labeled_vertex_edges(value->edges_in_by_label); + free_labeled_vertex_edges(value->edges_out_by_label); + free_labeled_vertex_edges(value->edges_self_by_label); value->edges_in = NULL; value->edges_out = NULL; value->edges_self = NULL; + value->edges_in_by_label = NULL; + value->edges_out_by_label = NULL; + value->edges_self_by_label = NULL; /* move to the next vertex */ curr_vertex = next_vertex; @@ -1448,6 +1534,26 @@ ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve) return ve->edges_self; } +ListGraphId *get_vertex_entry_edges_in_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_in_by_label, + edge_label_table_oid); +} + +ListGraphId *get_vertex_entry_edges_out_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_out_by_label, + edge_label_table_oid); +} + +ListGraphId *get_vertex_entry_edges_self_for_label(vertex_entry *ve, + Oid edge_label_table_oid) +{ + return get_labeled_vertex_edges(ve->edges_self_by_label, + edge_label_table_oid); +} Oid get_vertex_entry_label_table_oid(vertex_entry *ve) { diff --git a/src/backend/utils/adt/age_vle.c b/src/backend/utils/adt/age_vle.c index 1abe52c42..ccb4a8dd0 100644 --- a/src/backend/utils/adt/age_vle.c +++ b/src/backend/utils/adt/age_vle.c @@ -83,6 +83,8 @@ #define EXISTS_HTAB_NAME "known edges" #define EXISTS_HTAB_NAME_MIN_SIZE 16 #define EDGE_UNIQUENESS_FAST_ARGS 16 +#define EDGE_UNIQUENESS_NESTED_SCAN_LIMIT 64 +#define VLE_NEXT_VERTEX_UNKNOWN ((graphid)0) #define MAXIMUM_NUMBER_OF_CACHED_LOCAL_CONTEXTS 5 /* edge state entry for the edge_state_hashtable */ @@ -122,15 +124,23 @@ typedef struct VLE_local_context int num_edge_property_constraints; /* number of edge property constraints */ Datum edge_property_constraint_datum; /* edge property constraint as Datum */ uint32 edge_property_constraint_hash; /* edge property constraint hash */ + agtype_pair *edge_property_constraint_pairs; /* cached constraints */ + int num_cached_edge_property_constraints; /* cached constraint count */ + HTAB *edge_property_relation_cache; /* relation cache for property fetch */ int64 lidx; /* lower (start) bound index */ int64 uidx; /* upper (end) bound index */ bool uidx_infinite; /* flag if the upper bound is omitted */ cypher_rel_dir edge_direction; /* the direction of the edge */ HTAB *edge_state_hashtable; /* local state hashtable for our edges */ + int64 edge_state_initial_size; /* initial edge-state hash size estimate */ GraphIdStack *dfs_vertex_stack; /* dfs stack for vertices (array-based) */ GraphIdStack *dfs_edge_stack; /* dfs stack for edges (array-based) */ + GraphIdStack *dfs_next_vertex_stack; /* cached terminal vertices */ GraphIdStack *dfs_path_stack; /* dfs stack containing the path (array-based) */ + GraphIdStack *dfs_path_vertex_stack; /* vertices in current path order */ VLE_path_function path_function; /* which path function to use */ + bool reverse_paths_to; /* traverse paths-to from the bound end */ + bool reverse_output_path; /* reverse traversal result before return */ GraphIdNode *next_vertex; /* for VLE_FUNCTION_PATHS_TO */ int64 vle_grammar_node_id; /* the unique VLE grammar assigned node id */ bool use_cache; /* are we using VLE_local_context cache */ @@ -179,6 +189,13 @@ static bool get_agtype_scalar_bool_arg(agtype *agt_arg, const char *type_error_msg); static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, HTAB *relation_cache); +static void cache_edge_property_constraints(VLE_local_context *vlelctx); +static void free_edge_property_constraint_cache(VLE_local_context *vlelctx); +static bool cached_edge_property_constraints_match( + VLE_local_context *vlelctx, agtype_container *edge_properties); +static void free_cached_property_value(agtype_value *value); +static bool agtype_value_satisfies_property_constraint( + agtype_value *property_value, agtype_value *constraint_value); /* VLE local context functions */ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, FuncCallContext *funcctx); @@ -202,18 +219,49 @@ static int get_edge_uniqueness_args_fast(FunctionCallInfo fcinfo, Oid *fast_types, bool *fast_nulls); static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, int argno, int nargs); -static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx); static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id); +static void add_valid_vertex_edges_for_entry(VLE_local_context *vlelctx, + vertex_entry *ve); static graphid get_next_vertex(VLE_local_context *vlelctx, edge_entry *ee); +static graphid get_next_vertex_from_source(VLE_local_context *vlelctx, + edge_entry *ee, + graphid source_vertex_id); +static cypher_rel_dir reverse_edge_direction(cypher_rel_dir edge_direction); +static bool is_zero_length_only(VLE_local_context *vlelctx); +static bool is_empty_length_range(VLE_local_context *vlelctx); +static bool should_emit_zero_bound(VLE_local_context *vlelctx); +static int64 get_initial_edge_count(VLE_local_context *vlelctx, + graphid vertex_id, + cypher_rel_dir edge_direction); +static int64 get_matching_initial_edge_count(ListGraphId *edges); static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, int64 stack_size); /* VLE path and edge building functions */ static VLE_path_container *create_VLE_path_container(int64 path_size); static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx); +static VLE_path_container *build_reversed_VLE_path_container( + VLE_local_context *vlelctx); static VLE_path_container *build_VLE_zero_container(VLE_local_context *vlelctx); +static agtype_value *build_vle_edge_value(GRAPH_global_context *ggctx, + graphid edge_id, + HTAB *relation_cache); +static agtype_value *build_vle_vertex_value(GRAPH_global_context *ggctx, + graphid vertex_id, + HTAB *relation_cache); static agtype_value *build_path(VLE_path_container *vpc); static agtype_value *build_edge_list(VLE_path_container *vpc); +static agtype_value *build_empty_agtype_value_array(void); +static int64 agtv_vle_node_count(agtype *agt_arg_vpc); +static bool get_vle_edge_id_at_index(agtype *agt_arg_vpc, + int64 edge_index, graphid *edge_id); +static agtype_value *agtv_materialize_vle_vertex_at(agtype *agt_arg_vpc, + int64 node_index); +static agtype_value *agtv_materialize_vle_edge_endpoint_at( + agtype *agt_arg_vpc, int64 edge_index, bool start_endpoint); +static Datum age_vle_edge_endpoint_id_at(FunctionCallInfo fcinfo, + bool start_endpoint); +static agtype *build_empty_agtype_array(void); /* VLE_local_context cache management */ static VLE_local_context *get_cached_VLE_local_context(int64 vle_node_id); static void cache_VLE_local_context(VLE_local_context *vlelctx); @@ -385,8 +433,9 @@ static void create_VLE_local_state_hashtable(VLE_local_context *vlelctx) edge_state_ctl.entrysize = sizeof(edge_state_entry); edge_state_ctl.hash = tag_hash; - initial_size = Max(get_graph_num_loaded_edges(vlelctx->ggctx), + initial_size = Max(vlelctx->edge_state_initial_size, EDGE_STATE_HTAB_MIN_SIZE); + initial_size = Min(initial_size, get_graph_num_loaded_edges(vlelctx->ggctx)); vlelctx->edge_state_hashtable = hash_create(eshn, initial_size, &edge_state_ctl, HASH_ELEM | HASH_FUNCTION); @@ -405,7 +454,6 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, agtype_container *agtc_edge_property_constraint = NULL; agtype_iterator *constraint_it = NULL; agtype_iterator *property_it = NULL; - Oid edge_label_name_oid = InvalidOid; int num_edge_property_constraints = 0; int num_edge_properties = 0; @@ -413,34 +461,10 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, num_edge_property_constraints = vlelctx->num_edge_property_constraints; /* - * We only care about verifying that we have all of the property conditions. - * We don't care about extra unmatched properties. If there aren't any edge - * constraints, then the edge passes by default. - */ - if (vlelctx->edge_label_name_oid == InvalidOid && - num_edge_property_constraints == 0) - { - return true; - } - - /* get the edge label oid */ - edge_label_name_oid = get_edge_entry_label_table_oid(ee); - - /* - * Check for a label constraint. Remember, if the constraint label oid is - * InvalidOid, there isn't one. If there is one, they need to match. - */ - if (vlelctx->edge_label_name_oid != InvalidOid && - vlelctx->edge_label_name_oid != edge_label_name_oid) - { - return false; - } - - /* - * Fast path: if the label matched (or wasn't constrained) and there - * are no property constraints, the edge is a match. This avoids - * accessing edge properties entirely for label-only VLE patterns - * like [:KNOWS*1..3] which are the common case. + * add_valid_vertex_edges() handles direction selection, label selection, + * and cycle pruning before consulting edge-state match status. This helper + * only verifies property constraints against an already label-compatible + * candidate. */ if (num_edge_property_constraints == 0) { @@ -477,8 +501,16 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, */ if (num_edge_property_constraints == num_edge_properties) { - uint32 edge_props_hash = datum_image_hash(edge_props_datum, - false, -1); + uint32 edge_props_hash = 0; + + if (VARSIZE_ANY(edge_property) != + VARSIZE_ANY(vlelctx->edge_property_constraint)) + { + return false; + } + + edge_props_hash = datum_image_hash(edge_props_datum, false, -1); + /* check the hash first */ if (vlelctx->edge_property_constraint_hash == edge_props_hash) { @@ -494,6 +526,12 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, return false; } + if (vlelctx->num_cached_edge_property_constraints > 0) + { + return cached_edge_property_constraints_match( + vlelctx, agtc_edge_property); + } + /* get the iterators */ constraint_it = agtype_iterator_init(agtc_edge_property_constraint); property_it = agtype_iterator_init(agtc_edge_property); @@ -503,6 +541,166 @@ static bool is_an_edge_match(VLE_local_context *vlelctx, edge_entry *ee, } } +static void cache_edge_property_constraints(VLE_local_context *vlelctx) +{ + agtype_iterator *constraint_it = NULL; + agtype_iterator_token token; + agtype_pair *pairs = NULL; + agtype_value agtv; + int index; + + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; + + if (vlelctx->num_edge_property_constraints <= 0) + { + return; + } + + pairs = palloc0(sizeof(agtype_pair) * + vlelctx->num_edge_property_constraints); + + constraint_it = agtype_iterator_init( + &vlelctx->edge_property_constraint->root); + token = agtype_iterator_next(&constraint_it, &agtv, true); + Assert(token == WAGT_BEGIN_OBJECT); + + for (index = 0; index < vlelctx->num_edge_property_constraints; index++) + { + token = agtype_iterator_next(&constraint_it, &pairs[index].key, true); + Assert(token == WAGT_KEY); + + token = agtype_iterator_next(&constraint_it, &pairs[index].value, true); + Assert(token == WAGT_VALUE); + } + + token = agtype_iterator_next(&constraint_it, &agtv, true); + Assert(token == WAGT_END_OBJECT); + + vlelctx->edge_property_constraint_pairs = pairs; + vlelctx->num_cached_edge_property_constraints = + vlelctx->num_edge_property_constraints; + +} + +static void free_edge_property_constraint_cache(VLE_local_context *vlelctx) +{ + int index; + + if (vlelctx->edge_property_constraint_pairs == NULL) + { + vlelctx->num_cached_edge_property_constraints = 0; + return; + } + + for (index = 0; index < vlelctx->num_cached_edge_property_constraints; + index++) + { + agtype_pair *pair = &vlelctx->edge_property_constraint_pairs[index]; + + pfree_if_not_null(pair->key.val.string.val); + free_cached_property_value(&pair->value); + } + + pfree(vlelctx->edge_property_constraint_pairs); + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; +} + +static bool cached_edge_property_constraints_match( + VLE_local_context *vlelctx, agtype_container *edge_properties) +{ + int index; + + for (index = 0; index < vlelctx->num_cached_edge_property_constraints; + index++) + { + agtype_pair *constraint = + &vlelctx->edge_property_constraint_pairs[index]; + agtype_value property_value; + bool property_value_needs_free = false; + bool found; + bool matches; + + found = find_agtype_value_from_container_no_copy( + edge_properties, AGT_FOBJECT, &constraint->key, &property_value, + &property_value_needs_free); + if (!found) + { + return false; + } + + matches = agtype_value_satisfies_property_constraint( + &property_value, &constraint->value); + + if (property_value_needs_free) + { + pfree_agtype_value_content(&property_value); + } + + if (!matches) + { + return false; + } + } + + return true; +} + +static void free_cached_property_value(agtype_value *value) +{ + switch (value->type) + { + case AGTV_STRING: + pfree_if_not_null(value->val.string.val); + break; + + case AGTV_NUMERIC: + pfree_if_not_null(value->val.numeric); + break; + + case AGTV_VERTEX: + case AGTV_EDGE: + case AGTV_PATH: + case AGTV_ARRAY: + case AGTV_OBJECT: + pfree_agtype_value_content(value); + break; + + default: + break; + } +} + +static bool agtype_value_satisfies_property_constraint( + agtype_value *property_value, agtype_value *constraint_value) +{ + if (property_value->type != constraint_value->type) + { + return false; + } + + if (IS_A_AGTYPE_SCALAR(property_value)) + { + return compare_agtype_scalar_values(property_value, + constraint_value) == 0; + } + + Assert(property_value->type == AGTV_BINARY); + Assert(constraint_value->type == AGTV_BINARY); + + { + agtype_iterator *property_it = NULL; + agtype_iterator *constraint_it = NULL; + + property_it = agtype_iterator_init(property_value->val.binary.data); + constraint_it = agtype_iterator_init( + constraint_value->val.binary.data); + + return agtype_deep_contains(&property_it, &constraint_it, false); + } +} + static bool get_vle_vertex_or_id_arg(FunctionCallInfo fcinfo, int argno, const char *type_error_msg, graphid *result) @@ -720,9 +918,17 @@ static void free_VLE_local_context(VLE_local_context *vlelctx) vlelctx->edge_label_name = NULL; } - /* we need to free our state hashtable */ - hash_destroy(vlelctx->edge_state_hashtable); - vlelctx->edge_state_hashtable = NULL; + /* zero-length-only VLE never creates edge-state entries */ + if (vlelctx->edge_state_hashtable != NULL) + { + hash_destroy(vlelctx->edge_state_hashtable); + vlelctx->edge_state_hashtable = NULL; + } + + destroy_entry_property_relation_cache( + vlelctx->edge_property_relation_cache); + vlelctx->edge_property_relation_cache = NULL; + free_edge_property_constraint_cache(vlelctx); /* * Free the DFS stacks. When is_dirty is false, the stacks are in the @@ -738,54 +944,64 @@ static void free_VLE_local_context(VLE_local_context *vlelctx) { free_gid_stack(vlelctx->dfs_edge_stack); } + if (vlelctx->dfs_next_vertex_stack != NULL) + { + free_gid_stack(vlelctx->dfs_next_vertex_stack); + } if (vlelctx->dfs_path_stack != NULL) { free_gid_stack(vlelctx->dfs_path_stack); } + if (vlelctx->dfs_path_vertex_stack != NULL) + { + free_gid_stack(vlelctx->dfs_path_vertex_stack); + } vlelctx->dfs_vertex_stack = NULL; vlelctx->dfs_edge_stack = NULL; + vlelctx->dfs_next_vertex_stack = NULL; vlelctx->dfs_path_stack = NULL; + vlelctx->dfs_path_vertex_stack = NULL; /* and finally the context itself */ pfree_if_not_null(vlelctx); vlelctx = NULL; } -/* helper function to check if our start and end vertices exist */ -static bool do_vsid_and_veid_exist(VLE_local_context *vlelctx) -{ - /* if we are only using the starting vertex */ - if (vlelctx->path_function == VLE_FUNCTION_PATHS_FROM || - vlelctx->path_function == VLE_FUNCTION_PATHS_ALL) - { - return (get_vertex_entry(vlelctx->ggctx, vlelctx->vsid) != NULL); - } - - /* if we are only using the ending vertex */ - if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) - { - return (get_vertex_entry(vlelctx->ggctx, vlelctx->veid) != NULL); - } - - /* if we are using both start and end */ - return ((get_vertex_entry(vlelctx->ggctx, vlelctx->vsid) != NULL) && - (get_vertex_entry(vlelctx->ggctx, vlelctx->veid) != NULL)); -} - /* load the initial edges into the dfs_edge_stack */ static void load_initial_dfs_stacks(VLE_local_context *vlelctx) { + vertex_entry *start_vertex = NULL; + + vlelctx->dfs_path_vertex_stack->size = 0; + gid_stack_push(vlelctx->dfs_path_vertex_stack, vlelctx->vsid); + /* * If either the vsid or veid don't exist - don't load anything because * there won't be anything to find. */ - if (!do_vsid_and_veid_exist(vlelctx)) + if (is_empty_length_range(vlelctx) || + is_zero_length_only(vlelctx)) + { + return; + } + + start_vertex = get_vertex_entry(vlelctx->ggctx, vlelctx->vsid); + if (start_vertex == NULL) + { + return; + } + + if ((vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN || + (vlelctx->path_function == VLE_FUNCTION_PATHS_TO && + !vlelctx->reverse_paths_to)) && + vlelctx->veid != vlelctx->vsid && + get_vertex_entry(vlelctx->ggctx, vlelctx->veid) == NULL) { return; } /* add in the edges for the start vertex */ - add_valid_vertex_edges(vlelctx, vlelctx->vsid); + add_valid_vertex_edges_for_entry(vlelctx, start_vertex); } /* @@ -809,6 +1025,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, int64 vle_grammar_node_id = 0; bool use_cache = false; graphid vertex_id_arg; + int64 selected_start_edge_count = 0; /* * Get the VLE grammar node id, if it exists. Remember, we overload the @@ -848,14 +1065,17 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, "start vertex argument must be a vertex or the integer id", &vertex_id_arg)) { - /* if there are no more vertices to process, return NULL */ - if (vlelctx->next_vertex == NULL) + if (!vlelctx->reverse_paths_to) { - return NULL; + /* if there are no more vertices to process, return NULL */ + if (vlelctx->next_vertex == NULL) + { + return NULL; + } + vlelctx->vsid = get_graphid(vlelctx->next_vertex); + /* increment to the next vertex */ + vlelctx->next_vertex = next_GraphIdNode(vlelctx->next_vertex); } - vlelctx->vsid = get_graphid(vlelctx->next_vertex); - /* increment to the next vertex */ - vlelctx->next_vertex = next_GraphIdNode(vlelctx->next_vertex); } else { @@ -873,14 +1093,29 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, else { vlelctx->veid = vertex_id_arg; + if (vlelctx->reverse_paths_to) + { + vlelctx->vsid = vertex_id_arg; + vlelctx->next_vertex = NULL; + } + } + if (!vlelctx->reverse_paths_to && vlelctx->reverse_output_path) + { + graphid original_vsid = vlelctx->vsid; + + vlelctx->vsid = vlelctx->veid; + vlelctx->veid = original_vsid; } vlelctx->is_dirty = true; /* we need the SRF context to add in the edges to the stacks */ oldctx = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - /* load the initial edges into the dfs stacks */ - load_initial_dfs_stacks(vlelctx); + if (!is_empty_length_range(vlelctx)) + { + /* load the initial edges into the dfs stacks */ + load_initial_dfs_stacks(vlelctx); + } /* switch back to the original context */ MemoryContextSwitchTo(oldctx); @@ -925,7 +1160,7 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, graph_oid); /* allocate and initialize local VLE context */ - vlelctx = palloc(sizeof(VLE_local_context)); + vlelctx = palloc0(sizeof(VLE_local_context)); /* store the cache usage */ vlelctx->use_cache = use_cache; @@ -942,6 +1177,8 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, /* initialize the path function */ vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; + vlelctx->reverse_paths_to = false; + vlelctx->reverse_output_path = false; /* initialize the next vertex, in this case the first */ vlelctx->next_vertex = peek_stack_head(get_graph_vertices(ggctx)); @@ -995,7 +1232,10 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, } else { - vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; + if (vlelctx->path_function != VLE_FUNCTION_PATHS_TO) + { + vlelctx->path_function = VLE_FUNCTION_PATHS_BETWEEN; + } vlelctx->veid = vertex_id_arg; } @@ -1006,16 +1246,30 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, /* get the edge prototype's property conditions */ agtv_object = AGTYPE_EDGE_GET_PROPERTIES(&agtv_temp); - agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); - /* store the properties as an agtype */ - vlelctx->edge_property_constraint = agt_edge_property_constraint; vlelctx->num_edge_property_constraints = - AGT_ROOT_COUNT(agt_edge_property_constraint); - - d_edge_property_constraint = AGTYPE_P_GET_DATUM(agt_edge_property_constraint); - vlelctx->edge_property_constraint_datum = d_edge_property_constraint; - vlelctx->edge_property_constraint_hash = datum_image_hash(d_edge_property_constraint, false, -1); + agtv_object->val.object.num_pairs; + vlelctx->edge_property_relation_cache = NULL; + if (vlelctx->num_edge_property_constraints > 0) + { + agt_edge_property_constraint = agtype_value_to_agtype(agtv_object); + vlelctx->edge_property_constraint = agt_edge_property_constraint; + d_edge_property_constraint = + AGTYPE_P_GET_DATUM(agt_edge_property_constraint); + vlelctx->edge_property_constraint_datum = + d_edge_property_constraint; + vlelctx->edge_property_constraint_hash = + datum_image_hash(d_edge_property_constraint, false, -1); + cache_edge_property_constraints(vlelctx); + } + else + { + vlelctx->edge_property_constraint = NULL; + vlelctx->edge_property_constraint_datum = 0; + vlelctx->edge_property_constraint_hash = 0; + vlelctx->edge_property_constraint_pairs = NULL; + vlelctx->num_cached_edge_property_constraints = 0; + } /* get the edge prototype's label name */ agtv_object = AGTYPE_EDGE_GET_LABEL(&agtv_temp); @@ -1089,16 +1343,93 @@ static VLE_local_context *build_local_vle_context(FunctionCallInfo fcinfo, &agtv_temp_needs_free); vlelctx->edge_direction = agtv_temp.val.int_value; - /* create the local state hashtable */ - create_VLE_local_state_hashtable(vlelctx); + /* + * A paths-to query has a selective terminal vertex but no useful start + * vertex. Traverse from the bound end with the edge direction reversed and + * later reverse the emitted path container back to Cypher's left-to-right + * order. This avoids repeatedly launching DFS from every graph vertex. + */ + if (vlelctx->path_function == VLE_FUNCTION_PATHS_TO) + { + vlelctx->reverse_paths_to = true; + vlelctx->reverse_output_path = true; + vlelctx->vsid = vlelctx->veid; + vlelctx->next_vertex = NULL; + vlelctx->edge_direction = + reverse_edge_direction(vlelctx->edge_direction); + if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) + { + selected_start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + } + } + /* + * For two-bound VLE, choose the cheaper endpoint as the traversal root. + * Lower-zero queries are safe here because zero-path endpoint semantics are + * handled inside should_emit_zero_bound(). + */ + else if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx) && + vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN) + { + int64 start_edge_count; + int64 end_edge_count; + + start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + end_edge_count = get_initial_edge_count( + vlelctx, vlelctx->veid, + reverse_edge_direction(vlelctx->edge_direction)); - /* initialize the dfs stacks */ - vlelctx->dfs_vertex_stack = new_gid_stack(); - vlelctx->dfs_edge_stack = new_gid_stack(); - vlelctx->dfs_path_stack = new_gid_stack(); + if (end_edge_count < start_edge_count) + { + graphid original_vsid = vlelctx->vsid; + + vlelctx->vsid = vlelctx->veid; + vlelctx->veid = original_vsid; + vlelctx->edge_direction = + reverse_edge_direction(vlelctx->edge_direction); + vlelctx->reverse_output_path = true; + selected_start_edge_count = end_edge_count; + } + else + { + selected_start_edge_count = start_edge_count; + } + } + else if (!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) + { + selected_start_edge_count = get_initial_edge_count( + vlelctx, vlelctx->vsid, vlelctx->edge_direction); + } - /* load in the starting edge(s) */ - load_initial_dfs_stacks(vlelctx); + if ((!is_empty_length_range(vlelctx) && + !is_zero_length_only(vlelctx)) || + use_cache) + { + vlelctx->edge_state_initial_size = Max( + selected_start_edge_count * Max(vlelctx->uidx_infinite ? 1 : + vlelctx->uidx, 1), + EDGE_STATE_HTAB_MIN_SIZE); + + /* create the local state hashtable */ + create_VLE_local_state_hashtable(vlelctx); + } + + if (!is_empty_length_range(vlelctx)) + { + /* initialize the dfs stacks */ + vlelctx->dfs_vertex_stack = new_gid_stack(); + vlelctx->dfs_edge_stack = new_gid_stack(); + vlelctx->dfs_next_vertex_stack = new_gid_stack(); + vlelctx->dfs_path_stack = new_gid_stack(); + vlelctx->dfs_path_vertex_stack = new_gid_stack(); + + /* load in the starting edge(s) */ + load_initial_dfs_stacks(vlelctx); + } /* this is a new one so nothing follows it */ vlelctx->next = NULL; @@ -1311,97 +1642,264 @@ static graphid get_next_vertex(VLE_local_context *vlelctx, edge_entry *ee) return terminal_vertex_id; } -/* - * Helper function to find one path BETWEEN two vertices. - * - * Note: On the very first entry into this function, the starting vertex's edges - * should have already been loaded into the edge stack (this should have been - * done by the SRF initialization phase). - * - * This function will always return on either a valid path found (true) or none - * found (false). If one is found, the position (vertex & edge) will still be in - * the stack. Each successive invocation within the SRF will then look for the - * next available path until there aren't any left. - */ -static bool dfs_find_a_path_between(VLE_local_context *vlelctx) +static graphid get_next_vertex_from_source(VLE_local_context *vlelctx, + edge_entry *ee, + graphid source_vertex_id) { - GraphIdStack *vertex_stack = NULL; - GraphIdStack *edge_stack = NULL; - GraphIdStack *path_stack = NULL; - graphid end_vertex_id; - - Assert(vlelctx != NULL); - - /* for ease of reading */ - vertex_stack = vlelctx->dfs_vertex_stack; - edge_stack = vlelctx->dfs_edge_stack; - path_stack = vlelctx->dfs_path_stack; - end_vertex_id = vlelctx->veid; - - /* while we have edges to process */ - while (!(gid_stack_is_empty(edge_stack))) + switch (vlelctx->edge_direction) { - graphid edge_id; - graphid next_vertex_id; - edge_state_entry *ese = NULL; - edge_entry *ee = NULL; - bool found = false; - int64 path_length; + case CYPHER_REL_DIR_RIGHT: + return get_edge_entry_end_vertex_id(ee); - /* get an edge, but leave it on the stack for now */ - edge_id = gid_stack_peek(edge_stack); - /* get the edge's state */ - ese = get_edge_state(vlelctx, edge_id); - /* - * If the edge is already in use, it means that the edge is in the path. - * So, we need to see if it is the last path entry (we are backing up - - * we need to remove the edge from the path stack and reset its state - * and from the edge stack as we are done with it) or an interior edge - * in the path (loop - we need to remove the edge from the edge stack - * and start with the next edge). - */ - if (ese->used_in_path) - { - graphid path_edge_id; + case CYPHER_REL_DIR_LEFT: + return get_edge_entry_start_vertex_id(ee); - /* get the edge id on the top of the path stack (last edge) */ - path_edge_id = gid_stack_peek(path_stack); - /* - * If the ids are the same, we're backing up. So, remove it from the - * path stack and reset used_in_path. - */ - if (edge_id == path_edge_id) + case CYPHER_REL_DIR_NONE: + if (get_edge_entry_start_vertex_id(ee) == source_vertex_id) { - gid_stack_pop(path_stack); - ese->used_in_path = false; + return get_edge_entry_end_vertex_id(ee); } - /* now remove it from the edge stack */ - gid_stack_pop(edge_stack); - /* - * Remove its source vertex, if we are looking at edges as - * un-directional. We only maintain the vertex stack when the - * edge_direction is CYPHER_REL_DIR_NONE. This is to save space - * and time. - */ - if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + else if (get_edge_entry_end_vertex_id(ee) == source_vertex_id) { - gid_stack_pop(vertex_stack); + return get_edge_entry_start_vertex_id(ee); } - /* move to the next edge */ - continue; - } + elog(ERROR, "get_next_vertex_from_source: no parent match"); - /* - * Mark it and push it on the path stack. There is no need to push it on - * the edge stack as it is already there. - */ - ese->used_in_path = true; - gid_stack_push(path_stack, edge_id); + default: + elog(ERROR, "get_next_vertex_from_source: unknown edge direction"); + } +} + +static cypher_rel_dir reverse_edge_direction(cypher_rel_dir edge_direction) +{ + switch (edge_direction) + { + case CYPHER_REL_DIR_RIGHT: + return CYPHER_REL_DIR_LEFT; + + case CYPHER_REL_DIR_LEFT: + return CYPHER_REL_DIR_RIGHT; + + case CYPHER_REL_DIR_NONE: + return CYPHER_REL_DIR_NONE; + + default: + elog(ERROR, "reverse_edge_direction: unknown edge direction"); + } + + return CYPHER_REL_DIR_NONE; +} + +static bool is_zero_length_only(VLE_local_context *vlelctx) +{ + return vlelctx->lidx == 0 && + !vlelctx->uidx_infinite && + vlelctx->uidx == 0; +} + +static bool is_empty_length_range(VLE_local_context *vlelctx) +{ + return !vlelctx->uidx_infinite && + vlelctx->lidx > vlelctx->uidx; +} + +static bool should_emit_zero_bound(VLE_local_context *vlelctx) +{ + /* + * For two-bound VLE, zero length is valid only when both endpoints are the + * same vertex. When they differ, skip the zero path inside age_vle() so the + * parser does not need a terminal-edge post-filter just to discard it. + */ + if (vlelctx->path_function == VLE_FUNCTION_PATHS_BETWEEN) + { + return vlelctx->vsid == vlelctx->veid; + } + + return true; +} + +static int64 get_initial_edge_count(VLE_local_context *vlelctx, + graphid vertex_id, + cypher_rel_dir edge_direction) +{ + vertex_entry *ve = NULL; + int64 count = 0; + ListGraphId *edges = NULL; + + ve = get_vertex_entry(vlelctx->ggctx, vertex_id); + if (ve == NULL) + { + return 0; + } + + if (edge_direction == CYPHER_REL_DIR_RIGHT || + edge_direction == CYPHER_REL_DIR_NONE) + { + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_out_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_out(ve); + } + count += get_matching_initial_edge_count(edges); + } + if (edge_direction == CYPHER_REL_DIR_LEFT || + edge_direction == CYPHER_REL_DIR_NONE) + { + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_in_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_in(ve); + } + count += get_matching_initial_edge_count(edges); + } + + if (vlelctx->edge_label_name_oid != InvalidOid) + { + edges = get_vertex_entry_edges_self_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_self(ve); + } + count += get_matching_initial_edge_count(edges); + + return count; +} + +static int64 get_matching_initial_edge_count(ListGraphId *edges) +{ + if (edges == NULL) + { + return 0; + } + + /* + * Label-constrained callers pass a label-partitioned adjacency list, so + * every edge in the list already matches. Unlabeled callers need the raw + * adjacency size. In both cases ListGraphId tracks size, so avoid scanning + * the linked list during root-choice setup. + */ + return get_list_size(edges); +} + +/* + * Helper function to find one path BETWEEN two vertices. + * + * Note: On the very first entry into this function, the starting vertex's edges + * should have already been loaded into the edge stack (this should have been + * done by the SRF initialization phase). + * + * This function will always return on either a valid path found (true) or none + * found (false). If one is found, the position (vertex & edge) will still be in + * the stack. Each successive invocation within the SRF will then look for the + * next available path until there aren't any left. + */ +static bool dfs_find_a_path_between(VLE_local_context *vlelctx) +{ + GraphIdStack *vertex_stack = NULL; + GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; + GraphIdStack *path_stack = NULL; + GraphIdStack *path_vertex_stack = NULL; + graphid end_vertex_id; + + Assert(vlelctx != NULL); + + /* for ease of reading */ + vertex_stack = vlelctx->dfs_vertex_stack; + edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; + path_stack = vlelctx->dfs_path_stack; + path_vertex_stack = vlelctx->dfs_path_vertex_stack; + end_vertex_id = vlelctx->veid; + + /* while we have edges to process */ + while (!(gid_stack_is_empty(edge_stack))) + { + graphid edge_id; + graphid next_vertex_id; + edge_state_entry *ese = NULL; + edge_entry *ee = NULL; + graphid cached_next_vertex_id; + bool found = false; + int64 path_length; + + Assert(gid_stack_size(edge_stack) == gid_stack_size(next_vertex_stack)); + + /* get an edge, but leave it on the stack for now */ + edge_id = gid_stack_peek(edge_stack); + cached_next_vertex_id = gid_stack_peek(next_vertex_stack); + /* get the edge's state */ + ese = get_edge_state(vlelctx, edge_id); + /* + * If the edge is already in use, it means that the edge is in the path. + * So, we need to see if it is the last path entry (we are backing up - + * we need to remove the edge from the path stack and reset its state + * and from the edge stack as we are done with it) or an interior edge + * in the path (loop - we need to remove the edge from the edge stack + * and start with the next edge). + */ + if (ese->used_in_path) + { + graphid path_edge_id; + + /* get the edge id on the top of the path stack (last edge) */ + path_edge_id = gid_stack_peek(path_stack); + /* + * If the ids are the same, we're backing up. So, remove it from the + * path stack and reset used_in_path. + */ + if (edge_id == path_edge_id) + { + gid_stack_pop(path_stack); + gid_stack_pop(path_vertex_stack); + ese->used_in_path = false; + } + /* now remove it from the edge stack */ + gid_stack_pop(edge_stack); + gid_stack_pop(next_vertex_stack); + /* + * Remove its source vertex, if we are looking at edges as + * un-directional. We only maintain the vertex stack when the + * edge_direction is CYPHER_REL_DIR_NONE. This is to save space + * and time. + */ + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + gid_stack_pop(vertex_stack); + } + /* move to the next edge */ + continue; + } + + /* + * Mark it and push it on the path stack. There is no need to push it on + * the edge stack as it is already there. + */ + ese->used_in_path = true; + gid_stack_push(path_stack, edge_id); path_length = gid_stack_size(path_stack); - /* now get the edge entry so we can get the next vertex to move to */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - next_vertex_id = get_next_vertex(vlelctx, ee); + if (cached_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = cached_next_vertex_id; + } + else + { + /* get the edge entry so we can get the next vertex to move to */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + next_vertex_id = get_next_vertex(vlelctx, ee); + } + gid_stack_push(path_vertex_stack, next_vertex_id); /* * Is this the end of a path that meets our requirements? Is its length @@ -1459,14 +1957,18 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) { GraphIdStack *vertex_stack = NULL; GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; GraphIdStack *path_stack = NULL; + GraphIdStack *path_vertex_stack = NULL; Assert(vlelctx != NULL); /* for ease of reading */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; path_stack = vlelctx->dfs_path_stack; + path_vertex_stack = vlelctx->dfs_path_vertex_stack; /* while we have edges to process */ while (!(gid_stack_is_empty(edge_stack))) @@ -1475,11 +1977,15 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) graphid next_vertex_id; edge_state_entry *ese = NULL; edge_entry *ee = NULL; + graphid cached_next_vertex_id; bool found = false; int64 path_length; + Assert(gid_stack_size(edge_stack) == gid_stack_size(next_vertex_stack)); + /* get an edge, but leave it on the stack for now */ edge_id = gid_stack_peek(edge_stack); + cached_next_vertex_id = gid_stack_peek(next_vertex_stack); /* get the edge's state */ ese = get_edge_state(vlelctx, edge_id); /* @@ -1503,10 +2009,12 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) if (edge_id == path_edge_id) { gid_stack_pop(path_stack); + gid_stack_pop(path_vertex_stack); ese->used_in_path = false; } /* now remove it from the edge stack */ gid_stack_pop(edge_stack); + gid_stack_pop(next_vertex_stack); /* * Remove its source vertex, if we are looking at edges as * un-directional. We only maintain the vertex stack when the @@ -1529,9 +2037,17 @@ static bool dfs_find_a_path_from(VLE_local_context *vlelctx) gid_stack_push(path_stack, edge_id); path_length = gid_stack_size(path_stack); - /* now get the edge entry so we can get the next vertex to move to */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - next_vertex_id = get_next_vertex(vlelctx, ee); + if (cached_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = cached_next_vertex_id; + } + else + { + /* get the edge entry so we can get the next vertex to move to */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + next_vertex_id = get_next_vertex(vlelctx, ee); + } + gid_stack_push(path_vertex_stack, next_vertex_id); /* * Is this a path that meets our requirements? Is its length within the @@ -1571,12 +2087,13 @@ static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, int64 stack_size) { GraphIdStack *stack = vlelctx->dfs_path_stack; + graphid *path_edges = stack->array; int64 i; - /* scan the array-based path stack */ - for (i = 0; i < stack_size; i++) + /* Scan from the tail; immediate back edges are common during DFS. */ + for (i = stack_size; i > 0; i--) { - if (gid_stack_get(stack, i) == edge_id) + if (path_edges[i - 1] == edge_id) { return true; } @@ -1598,16 +2115,7 @@ static bool is_edge_in_path(VLE_local_context *vlelctx, graphid edge_id, static void add_valid_vertex_edges(VLE_local_context *vlelctx, graphid vertex_id) { - GraphIdStack *vertex_stack = NULL; - GraphIdStack *edge_stack = NULL; - ListGraphId *edges = NULL; vertex_entry *ve = NULL; - GraphIdNode *edge_in = NULL; - GraphIdNode *edge_out = NULL; - GraphIdNode *edge_self = NULL; - HTAB *relation_cache = NULL; - graphid source_vertex_id = 0; - int64 path_stack_size; /* get the vertex entry */ ve = get_vertex_entry(vlelctx->ggctx, vertex_id); @@ -1617,36 +2125,83 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, elog(ERROR, "add_valid_vertex_edges: no vertex found"); } + add_valid_vertex_edges_for_entry(vlelctx, ve); +} + +static void add_valid_vertex_edges_for_entry(VLE_local_context *vlelctx, + vertex_entry *ve) +{ + GraphIdStack *vertex_stack = NULL; + GraphIdStack *edge_stack = NULL; + GraphIdStack *next_vertex_stack = NULL; + ListGraphId *edges = NULL; + GraphIdNode *edge_in = NULL; + GraphIdNode *edge_out = NULL; + GraphIdNode *edge_self = NULL; + HTAB *relation_cache = NULL; + graphid source_vertex_id = 0; + int64 path_stack_size; + bool label_constrained = vlelctx->edge_label_name_oid != InvalidOid; + bool has_property_constraints = + vlelctx->num_edge_property_constraints > 0; + /* point to stacks */ vertex_stack = vlelctx->dfs_vertex_stack; edge_stack = vlelctx->dfs_edge_stack; + next_vertex_stack = vlelctx->dfs_next_vertex_stack; path_stack_size = gid_stack_size(vlelctx->dfs_path_stack); - if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) - { - source_vertex_id = get_vertex_entry_id(ve); - } + source_vertex_id = get_vertex_entry_id(ve); /* set to the first edge for each edge list for the specified direction */ if (vlelctx->edge_direction == CYPHER_REL_DIR_RIGHT || vlelctx->edge_direction == CYPHER_REL_DIR_NONE) { - edges = get_vertex_entry_edges_out(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_out_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_out(ve); + } edge_out = (edges != NULL) ? get_list_head(edges) : NULL; } if (vlelctx->edge_direction == CYPHER_REL_DIR_LEFT || vlelctx->edge_direction == CYPHER_REL_DIR_NONE) { - edges = get_vertex_entry_edges_in(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_in_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_in(ve); + } edge_in = (edges != NULL) ? get_list_head(edges) : NULL; } /* set to the first selfloop edge */ - edges = get_vertex_entry_edges_self(ve); + if (label_constrained) + { + edges = get_vertex_entry_edges_self_for_label( + ve, vlelctx->edge_label_name_oid); + } + else + { + edges = get_vertex_entry_edges_self(ve); + } edge_self = (edges != NULL) ? get_list_head(edges) : NULL; - if (vlelctx->num_edge_property_constraints > 0) + if (has_property_constraints) { - relation_cache = create_entry_property_relation_cache( - "VLE edge match relation cache"); + if (vlelctx->edge_property_relation_cache == NULL) + { + vlelctx->edge_property_relation_cache = + create_entry_property_relation_cache( + "VLE edge match relation cache"); + } + relation_cache = vlelctx->edge_property_relation_cache; } /* add in valid vertex edges */ @@ -1655,6 +2210,7 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, edge_entry *ee = NULL; edge_state_entry *ese = NULL; graphid edge_id; + graphid fast_next_vertex_id = VLE_NEXT_VERTEX_UNKNOWN; /* get the edge_id from the next available edge*/ if (edge_out != NULL) @@ -1668,13 +2224,15 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, else { edge_id = get_graphid(edge_self); + fast_next_vertex_id = source_vertex_id; } /* * This is a fast existence check, relative to the hash search, for when * the path stack is small. If the edge is in the path, we skip it. */ - if (path_stack_size < 10 && + if (path_stack_size > 0 && + path_stack_size < 10 && is_edge_in_path(vlelctx, edge_id, path_stack_size)) { /* set to the next available edge */ @@ -1693,13 +2251,38 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, continue; } - /* get the edge entry */ - ee = get_edge_entry(vlelctx->ggctx, edge_id); - /* it better exist */ - if (ee == NULL) + /* + * When the current path is short, the direct path-stack scan above + * already proved that this edge is not currently used. If there are no + * property constraints, every edge from the selected adjacency list is + * a match, so defer edge-state hash creation until DFS actually + * consumes the edge. + */ + if (path_stack_size < 10 && + !has_property_constraints) { - elog(ERROR, "add_valid_vertex_edges: no edge found"); + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + gid_stack_push(vertex_stack, source_vertex_id); + } + gid_stack_push(edge_stack, edge_id); + gid_stack_push(next_vertex_stack, fast_next_vertex_id); + + if (edge_out != NULL) + { + edge_out = next_GraphIdNode(edge_out); + } + else if (edge_in != NULL) + { + edge_in = next_GraphIdNode(edge_in); + } + else + { + edge_self = next_GraphIdNode(edge_self); + } + continue; } + /* get its state */ ese = get_edge_state(vlelctx, edge_id); /* @@ -1710,19 +2293,37 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, { /* validate the edge if it hasn't been already */ if (!ese->has_been_matched && - is_an_edge_match(vlelctx, ee, relation_cache)) + !has_property_constraints) { ese->has_been_matched = true; ese->matched = true; } else if (!ese->has_been_matched) { + /* get the edge entry */ + ee = get_edge_entry(vlelctx->ggctx, edge_id); + /* it better exist */ + if (ee == NULL) + { + elog(ERROR, "add_valid_vertex_edges: no edge found"); + } + + if (is_an_edge_match(vlelctx, ee, relation_cache)) + { + ese->matched = true; + } + else + { + ese->matched = false; + } ese->has_been_matched = true; - ese->matched = false; } + /* if it is a match, add it */ if (ese->has_been_matched && ese->matched) { + graphid next_vertex_id; + /* * We need to maintain our source vertex for each edge added * if the edge_direction is CYPHER_REL_DIR_NONE. This is due @@ -1731,11 +2332,30 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, * un-directional VLE edge, you don't know the vertex that * you just came from. So, we need to store it. */ - if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) - { - gid_stack_push(vertex_stack, source_vertex_id); - } - gid_stack_push(edge_stack, edge_id); + if (vlelctx->edge_direction == CYPHER_REL_DIR_NONE) + { + gid_stack_push(vertex_stack, source_vertex_id); + } + if (fast_next_vertex_id != VLE_NEXT_VERTEX_UNKNOWN) + { + next_vertex_id = fast_next_vertex_id; + } + else + { + if (ee == NULL) + { + ee = get_edge_entry(vlelctx->ggctx, edge_id); + if (ee == NULL) + { + elog(ERROR, + "add_valid_vertex_edges: no edge found"); + } + } + next_vertex_id = get_next_vertex_from_source( + vlelctx, ee, source_vertex_id); + } + gid_stack_push(edge_stack, edge_id); + gid_stack_push(next_vertex_stack, next_vertex_id); } } /* get the next working edge */ @@ -1753,7 +2373,7 @@ static void add_valid_vertex_edges(VLE_local_context *vlelctx, } } - destroy_entry_property_relation_cache(relation_cache); + /* The relation cache is owned by the VLE context and freed with it. */ } /* @@ -1822,10 +2442,11 @@ static VLE_path_container *create_VLE_path_container(int64 path_size) static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx) { GraphIdStack *stack = vlelctx->dfs_path_stack; + GraphIdStack *vertex_stack = vlelctx->dfs_path_vertex_stack; VLE_path_container *vpc = NULL; graphid *graphid_array = NULL; - graphid vid = 0; - int index = 0; + graphid *edge_array = NULL; + graphid *vertex_array = NULL; int ssize = 0; int j = 0; @@ -1849,43 +2470,68 @@ static VLE_path_container *build_VLE_path_container(VLE_local_context *vlelctx) /* get the graphid_array from the container */ graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - /* get and store the start vertex */ - vid = vlelctx->vsid; - graphid_array[0] = vid; - /* - * Fill in edge entries from the back to the front. The path stack - * is array-based with index 0 = bottom (first pushed) and - * index size-1 = top (last pushed). We iterate from top to bottom - * to fill the graphid_array from back to front. + * The path edge stack and path vertex stack are both array based with + * index 0 = first path element. DFS already computed each next vertex, so + * building the VLE container does not need to look up every edge entry + * again just to recover endpoint ids. */ - index = vpc->graphid_array_size - 2; + Assert(gid_stack_size(vertex_stack) == ssize + 1); + edge_array = stack->array; + vertex_array = vertex_stack->array; - for (j = ssize - 1; j >= 0; j--) + for (j = 0; j < ssize; j++) { - /* 0 is the vsid, we should never get here */ - Assert(index > 0); + graphid_array[j * 2] = vertex_array[j]; + graphid_array[(j * 2) + 1] = edge_array[j]; + } + graphid_array[ssize * 2] = vertex_array[ssize]; - /* store the edge from stack position j */ - graphid_array[index] = gid_stack_get(stack, j); + /* return the container */ + return vpc; +} - /* we need to skip over the interior vertices */ - index -= 2; - } +static VLE_path_container *build_reversed_VLE_path_container( + VLE_local_context *vlelctx) +{ + GraphIdStack *stack = vlelctx->dfs_path_stack; + GraphIdStack *vertex_stack = vlelctx->dfs_path_vertex_stack; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + graphid *edge_array = NULL; + graphid *vertex_array = NULL; + int index = 0; + int ssize = 0; + int j = 0; - /* now add in the interior vertices, starting from the first edge */ - for (index = 1; index < vpc->graphid_array_size - 1; index += 2) + if (stack == NULL) { - edge_entry *ee = NULL; + return NULL; + } + + ssize = gid_stack_size(stack); + vpc = create_VLE_path_container((ssize * 2) + 1); + vpc->graph_oid = vlelctx->graph_oid; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + /* + * Reverse paths-to traversal starts at the Cypher right endpoint and walks + * backward. The path vertex stack stores traversal-order vertices, so the + * original Cypher order is the reverse of both stacks. + */ + Assert(gid_stack_size(vertex_stack) == ssize + 1); + edge_array = stack->array; + vertex_array = vertex_stack->array; - ee = get_edge_entry(vlelctx->ggctx, graphid_array[index]); - vid = (vid == get_edge_entry_start_vertex_id(ee)) ? - get_edge_entry_end_vertex_id(ee) : - get_edge_entry_start_vertex_id(ee); - graphid_array[index+1] = vid; + graphid_array[0] = vertex_array[ssize]; + index = 1; + for (j = ssize - 1; j >= 0; j--) + { + graphid_array[index] = edge_array[j]; + graphid_array[index + 1] = vertex_array[j]; + index += 2; } - /* return the container */ return vpc; } @@ -1940,6 +2586,12 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) int64 graphid_array_size = 0; int index = 0; + graphid_array_size = vpc->graphid_array_size; + if (graphid_array_size <= 1) + { + return build_empty_agtype_value_array(); + } + /* get the graph_oid */ graph_oid = vpc->graph_oid; @@ -1950,13 +2602,12 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) /* get the graphid_array and size */ graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); - graphid_array_size = vpc->graphid_array_size; /* initialize our agtype array */ MemSet(&edges_result, 0, sizeof(agtype_in_state)); edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_BEGIN_ARRAY, NULL); - if (graphid_array_size > 2) + if (graphid_array_size > 3) { relation_cache = create_entry_property_relation_cache( "vle edge materialization relation cache"); @@ -1964,19 +2615,10 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) for (index = 1; index < graphid_array_size - 1; index += 2) { - char *label_name = NULL; - edge_entry *ee = NULL; agtype_value *agtv_edge = NULL; - /* get the edge entry from the hashtable */ - ee = get_edge_entry(ggctx, graphid_array[index]); - label_name = get_edge_entry_label_name(ee); - /* reconstruct the edge */ - agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, - get_edge_entry_end_vertex_id(ee), - get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties_with_cache( - ee, relation_cache)); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[index], + relation_cache); /* push the edge*/ edges_result.res = push_agtype_value(&edges_result.parse_state, WAGT_ELEM, agtv_edge); @@ -1994,6 +2636,69 @@ static agtype_value *build_edge_list(VLE_path_container *vpc) return edges_result.res; } +static agtype *build_empty_agtype_array(void) +{ + agtype_in_state result; + agtype *agt_result; + + MemSet(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agt_result = agtype_value_to_agtype(result.res); + + pfree_agtype_in_state(&result); + + return agt_result; +} + +static agtype_value *build_empty_agtype_value_array(void) +{ + agtype_in_state result; + + MemSet(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + result.res->type = AGTV_ARRAY; + + return result.res; +} + +static agtype_value *build_vle_edge_value(GRAPH_global_context *ggctx, + graphid edge_id, + HTAB *relation_cache) +{ + edge_entry *ee = NULL; + + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + return agtype_value_build_edge(get_edge_entry_id(ee), + get_edge_entry_label_name(ee), + get_edge_entry_end_vertex_id(ee), + get_edge_entry_start_vertex_id(ee), + get_edge_entry_properties_with_cache( + ee, relation_cache)); +} + +static agtype_value *build_vle_vertex_value(GRAPH_global_context *ggctx, + graphid vertex_id, + HTAB *relation_cache) +{ + vertex_entry *ve = NULL; + + ve = get_vertex_entry(ggctx, vertex_id); + Assert(ve != NULL); + + return agtype_value_build_vertex(get_vertex_entry_id(ve), + get_vertex_entry_label_name(ve), + get_vertex_entry_properties_with_cache( + ve, relation_cache)); +} + /* * Helper function to build an array of type AGTV_PATH from an array of * graphids. @@ -2005,7 +2710,7 @@ static agtype_value *build_path(VLE_path_container *vpc) { GRAPH_global_context *ggctx = NULL; agtype_in_state path_result; - HTAB *relation_cache; + HTAB *relation_cache = NULL; Oid graph_oid = InvalidOid; graphid *graphid_array = NULL; int64 graphid_array_size = 0; @@ -2027,25 +2732,20 @@ static agtype_value *build_path(VLE_path_container *vpc) MemSet(&path_result, 0, sizeof(agtype_in_state)); path_result.res = push_agtype_value(&path_result.parse_state, WAGT_BEGIN_ARRAY, NULL); - relation_cache = create_entry_property_relation_cache( - "vle path materialization relation cache"); + if (graphid_array_size > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle path materialization relation cache"); + } for (index = 0; index < graphid_array_size; index += 2) { - char *label_name = NULL; - vertex_entry *ve = NULL; - edge_entry *ee = NULL; agtype_value *agtv_vertex = NULL; agtype_value *agtv_edge = NULL; - /* get the vertex entry from the hashtable */ - ve = get_vertex_entry(ggctx, graphid_array[index]); - label_name = get_vertex_entry_label_name(ve); /* reconstruct the vertex */ - agtv_vertex = agtype_value_build_vertex(get_vertex_entry_id(ve), - label_name, - get_vertex_entry_properties_with_cache( - ve, relation_cache)); + agtv_vertex = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); /* push the vertex */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_vertex); @@ -2059,15 +2759,9 @@ static agtype_value *build_path(VLE_path_container *vpc) break; } - /* get the edge entry from the hashtable */ - ee = get_edge_entry(ggctx, graphid_array[index+1]); - label_name = get_edge_entry_label_name(ee); /* reconstruct the edge */ - agtv_edge = agtype_value_build_edge(get_edge_entry_id(ee), label_name, - get_edge_entry_end_vertex_id(ee), - get_edge_entry_start_vertex_id(ee), - get_edge_entry_properties_with_cache( - ee, relation_cache)); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[index + 1], + relation_cache); /* push the edge*/ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_ELEM, agtv_edge); @@ -2076,7 +2770,10 @@ static agtype_value *build_path(VLE_path_container *vpc) /* close our agtype array */ path_result.res = push_agtype_value(&path_result.parse_state, WAGT_END_ARRAY, NULL); - destroy_entry_property_relation_cache(relation_cache); + if (relation_cache != NULL) + { + destroy_entry_property_relation_cache(relation_cache); + } /* make it a path */ path_result.res->type = AGTV_PATH; @@ -2170,8 +2867,12 @@ Datum age_vle(PG_FUNCTION_ARGS) */ funcctx->user_fctx = vlelctx; + if (is_empty_length_range(vlelctx)) + { + done = true; + } /* if we are starting from zero [*0..x] flag it */ - if (vlelctx->lidx == 0) + else if (vlelctx->lidx == 0 && should_emit_zero_bound(vlelctx)) { is_zero_bound = true; done = true; @@ -2196,6 +2897,12 @@ Datum age_vle(PG_FUNCTION_ARGS) switch (vlelctx->path_function) { case VLE_FUNCTION_PATHS_TO: + if (vlelctx->reverse_paths_to) + { + found_a_path = dfs_find_a_path_from(vlelctx); + break; + } + /* fall through */ case VLE_FUNCTION_PATHS_BETWEEN: found_a_path = dfs_find_a_path_between(vlelctx); break; @@ -2233,7 +2940,11 @@ Datum age_vle(PG_FUNCTION_ARGS) load_initial_dfs_stacks(vlelctx); /* if we are starting from zero [*0..x] flag it */ - if (vlelctx->lidx == 0) + if (is_empty_length_range(vlelctx)) + { + done = true; + } + else if (vlelctx->lidx == 0 && should_emit_zero_bound(vlelctx)) { is_zero_bound = true; done = true; @@ -2275,7 +2986,14 @@ Datum age_vle(PG_FUNCTION_ARGS) * path_stack. This will also correct for the path_stack being last * in, first out. */ - vpc = build_VLE_path_container(vlelctx); + if (vlelctx->reverse_output_path) + { + vpc = build_reversed_VLE_path_container(vlelctx); + } + else + { + vpc = build_VLE_path_container(vlelctx); + } } /* otherwise, this is the zero boundary case [*0..x] */ else @@ -2289,6 +3007,10 @@ Datum age_vle(PG_FUNCTION_ARGS) /* otherwise, we are done and we need to cleanup and signal done */ else { + destroy_entry_property_relation_cache( + vlelctx->edge_property_relation_cache); + vlelctx->edge_property_relation_cache = NULL; + /* mark local context as clean */ vlelctx->is_dirty = false; @@ -2355,6 +3077,66 @@ agtype_value *agtv_materialize_vle_path(agtype *agt_arg_vpc) return agtv_path; } +int64 agt_vle_append_path_interior(agtype *agt_arg_vpc, + agtype_in_state *result) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 graphid_array_size = 0; + int64 index; + int64 appended = 0; + + Assert(agt_arg_vpc != NULL); + Assert(result != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array_size = vpc->graphid_array_size; + if (graphid_array_size <= 1) + { + return 0; + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (graphid_array_size > 3) + { + relation_cache = create_entry_property_relation_cache( + "vle path interior materialization relation cache"); + } + + for (index = 1; index < graphid_array_size - 1; index++) + { + agtype_value *agtv_value = NULL; + + if (index % 2 == 1) + { + agtv_value = build_vle_edge_value(ggctx, graphid_array[index], + relation_cache); + } + else + { + agtv_value = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); + } + + result->res = push_agtype_value(&result->parse_state, WAGT_ELEM, + agtv_value); + appended++; + } + + destroy_entry_property_relation_cache(relation_cache); + + return appended; +} + /* PG function to match 2 VLE edges */ PG_FUNCTION_INFO_V1(age_match_two_vle_edges); @@ -2391,8 +3173,6 @@ Datum age_match_two_vle_edges(PG_FUNCTION_ARGS) left_array_size = left_path->graphid_array_size; left_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(left_path); - PG_FREE_IF_COPY(agt_arg_vpc, 0); - agt_arg_vpc = AG_GET_ARG_AGTYPE_P(1); if (!AGT_ROOT_IS_BINARY(agt_arg_vpc) || @@ -2407,13 +3187,15 @@ Datum age_match_two_vle_edges(PG_FUNCTION_ARGS) right_path = (VLE_path_container *)agt_arg_vpc; right_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(right_path); - PG_FREE_IF_COPY(agt_arg_vpc, 1); - if (left_array[left_array_size - 1] != right_array[0]) { + PG_FREE_IF_COPY(left_path, 0); + PG_FREE_IF_COPY(right_path, 1); PG_RETURN_BOOL(false); } + PG_FREE_IF_COPY(left_path, 0); + PG_FREE_IF_COPY(right_path, 1); PG_RETURN_BOOL(true); } @@ -2488,110 +3270,3826 @@ Datum age_match_vle_edge_to_id_qual(PG_FUNCTION_ARGS) gid = get_agtype_scalar_graphid_arg(edge_id, "argument 2 of age_match_vle_edge_to_edge_qual must be an integer"); } - else if (type1 == GRAPHIDOID) + else if (type1 == GRAPHIDOID) + { + gid = DATUM_GET_GRAPHID(PG_GETARG_DATUM(1)); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("match_vle_terminal_edge() argument 1 must be an agtype integer or a graphid"))); + } + + pos_agt = AG_GET_ARG_AGTYPE_P(2); + + vle_is_on_left = get_agtype_scalar_bool_arg(pos_agt, + "argument 3 of age_match_vle_edge_to_edge_qual must be an integer"); + + if (vle_is_on_left) + { + int array_size = vle_path->graphid_array_size; + + /* + * Path is like ...[vle_edge]-()-[regular_edge]... Get the graphid of + * the vertex at the endof the path and check that it matches the id + * that was passed in the second arg. The transform logic is responsible + * for making that the start or end id, depending on its direction. + */ + if (gid != array[array_size - 1]) + { + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); + } + else + { + /* + * Path is like ...[edge]-()-[vle_edge]... Get the vertex at the start + * of the vle edge and check against id. + */ + if (gid != array[0]) + { + PG_RETURN_BOOL(false); + } + + PG_RETURN_BOOL(true); + } +} + +/* + * Exposed helper function to make an agtype_value AGTV_ARRAY of edges from a + * VLE_path_container. + */ +agtype_value *agtv_materialize_vle_edges(agtype *agt_arg_vpc) +{ + VLE_path_container *vpc = NULL; + agtype_value *agtv_array = NULL; + + /* the passed argument should not be NULL */ + Assert(agt_arg_vpc != NULL); + + /* + * The path must be a binary container and the type of the object in the + * container must be an AGT_FBINARY_TYPE_VLE_PATH. + */ + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + /* get the container */ + vpc = (VLE_path_container *)agt_arg_vpc; + + /* it should not be null */ + Assert(vpc != NULL); + + /* build the AGTV_ARRAY of edges from the VLE_path_container */ + agtv_array = build_edge_list(vpc); + + return agtv_array; + +} + +agtype_value *agtv_materialize_vle_edge_at(agtype *agt_arg_vpc, + int64 edge_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 edge_count = 0; + Oid graph_oid = InvalidOid; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + return build_vle_edge_value(ggctx, graphid_array[(edge_index * 2) + 1], + NULL); +} + +static agtype_value *agtv_materialize_vle_vertex_at(agtype *agt_arg_vpc, + int64 node_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 node_count; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + return NULL; + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + return build_vle_vertex_value(ggctx, graphid_array[node_index * 2], + NULL); +} + +static agtype_value *agtv_materialize_vle_edge_endpoint_at( + agtype *agt_arg_vpc, int64 edge_index, bool start_endpoint) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + graphid *graphid_array = NULL; + graphid endpoint_id; + graphid edge_id; + int64 edge_count = 0; + Oid graph_oid = InvalidOid; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + return build_vle_vertex_value(ggctx, endpoint_id, NULL); +} + +agtype_value *agtv_materialize_vle_edges_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle edge slice materialization relation cache"); + } + + for (index = lower_index; index < upper_index; index++) + { + agtype_value *agtv_edge = NULL; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(index * 2) + 1], relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +static agtype_value *agtv_materialize_vle_edges_reversed_slice( + agtype *agt_arg_vpc, int64 lower_index, int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 edge_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle reversed edge slice materialization relation cache"); + } + + for (index = upper_index - 1; index >= lower_index; index--) + { + agtype_value *agtv_edge = NULL; + int64 original_index = edge_count - index - 1; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(original_index * 2) + 1], + relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +agtype_value *agtv_materialize_vle_edges_reversed(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state edges_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 edge_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + return build_empty_agtype_value_array(); + } + + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&edges_result, 0, sizeof(agtype_in_state)); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (edge_count > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle edge reverse materialization relation cache"); + } + + for (index = edge_count - 1; index >= 0; index--) + { + agtype_value *agtv_edge = NULL; + + agtv_edge = build_vle_edge_value( + ggctx, graphid_array[(index * 2) + 1], relation_cache); + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_ELEM, agtv_edge); + } + + edges_result.res = push_agtype_value(&edges_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + edges_result.res->type = AGTV_ARRAY; + + return edges_result.res; +} + +agtype *agt_vle_edge_properties_at(agtype *agt_arg_vpc, int64 edge_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return NULL; + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + return DATUM_GET_AGTYPE_P(get_edge_entry_properties(ee)); +} + +agtype_value *agtv_materialize_vle_nodes(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 graphid_array_size = 0; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + graphid_array_size = vpc->graphid_array_size; + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (graphid_array_size > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node materialization relation cache"); + } + + for (index = 0; index < graphid_array_size; index += 2) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value(ggctx, graphid_array[index], + relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node slice materialization relation cache"); + } + + for (index = lower_index; index < upper_index; index++) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_reversed_slice( + agtype *agt_arg_vpc, int64 lower_index, int64 upper_index) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 node_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (lower_index >= upper_index) + { + return build_empty_agtype_value_array(); + } + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_count = agtv_vle_node_count(agt_arg_vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (upper_index - lower_index > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle reversed node slice materialization relation cache"); + } + + for (index = upper_index - 1; index >= lower_index; index--) + { + agtype_value *agtv_vertex = NULL; + int64 original_index = node_count - index - 1; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[original_index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +static agtype_value *agtv_materialize_vle_nodes_reversed(agtype *agt_arg_vpc) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype_in_state nodes_result; + HTAB *relation_cache = NULL; + graphid *graphid_array = NULL; + Oid graph_oid = InvalidOid; + int64 node_count; + int64 index; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graph_oid = vpc->graph_oid; + ggctx = find_GRAPH_global_context(graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_count = agtv_vle_node_count(agt_arg_vpc); + + MemSet(&nodes_result, 0, sizeof(agtype_in_state)); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + + if (node_count > 1) + { + relation_cache = create_entry_property_relation_cache( + "vle node reverse materialization relation cache"); + } + + for (index = node_count - 1; index >= 0; index--) + { + agtype_value *agtv_vertex = NULL; + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[index * 2], relation_cache); + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_ELEM, agtv_vertex); + } + + nodes_result.res = push_agtype_value(&nodes_result.parse_state, + WAGT_END_ARRAY, NULL); + destroy_entry_property_relation_cache(relation_cache); + nodes_result.res->type = AGTV_ARRAY; + + return nodes_result.res; +} + +int64 agtv_vle_edge_count(agtype *agt_arg_vpc) +{ + VLE_path_container *vpc = NULL; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + + return (vpc->graphid_array_size - 1) / 2; +} + +bool agt_vle_contains_edge_id(agtype *agt_arg_vpc, graphid edge_id) +{ + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 graphid_array_size = 0; + int64 i; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + graphid_array_size = vpc->graphid_array_size; + + for (i = 1; i < graphid_array_size - 1; i += 2) + { + if (graphid_array[i] == edge_id) + { + return true; + } + } + + return false; +} + +static bool get_vle_edge_id_at_index(agtype *agt_arg_vpc, int64 edge_index, + graphid *edge_id) +{ + VLE_path_container *vpc = NULL; + graphid *graphid_array = NULL; + int64 edge_count; + + Assert(agt_arg_vpc != NULL); + Assert(edge_id != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + return false; + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + *edge_id = graphid_array[(edge_index * 2) + 1]; + + return true; +} + +static int64 agtv_vle_node_count(agtype *agt_arg_vpc) +{ + VLE_path_container *vpc = NULL; + + Assert(agt_arg_vpc != NULL); + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + + return (vpc->graphid_array_size + 1) / 2; +} + +/* PG wrapper function for agtv_materialize_vle_edges */ +PG_FUNCTION_INFO_V1(age_materialize_vle_edges); + +Datum age_materialize_vle_edges(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 edge_count; + + /* if we have a NULL VLE_path_container, return NULL */ + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + /* get the VLE_path_container argument */ + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + + /* if NULL, return NULL */ + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes); + +Datum age_materialize_vle_nodes(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_array = agtv_materialize_vle_nodes(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_vle_path_node_count); + +Datum age_vle_path_node_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_node_count(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_tail_count); + +Datum age_vle_edge_tail_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = edge_count > 0 ? edge_count - 1 : 0; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_list_is_empty); + +Datum age_vle_list_is_empty(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + bool mode_needs_free = false; + int64 edge_count; + int64 node_count; + int64 mode; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_is_empty", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + node_count = edge_count + 1; + + switch (mode) + { + case 0: + result = node_count == 0; + break; + + case 1: + result = edge_count == 0; + break; + + case 2: + result = node_count <= 1; + break; + + case 3: + result = edge_count <= 1; + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_is_empty: invalid mode"))); + } + + PG_RETURN_BOOL(result); +} + +PG_FUNCTION_INFO_V1(age_vle_list_slice_count); + +Datum age_vle_list_slice_count(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value agtv_result; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + break; + case 3: + list_count = edge_count; + break; + case 6: + list_count = Max(edge_count - 2, 0); + break; + case 7: + list_count = Max(edge_count - 1, 0); + break; + case 8: + list_count = Max(edge_count - 1, 0); + break; + case 9: + list_count = edge_count; + break; + case 10: + list_count = Max(edge_count - 1, 0); + break; + case 11: + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_slice_count: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_vle_list_slice_count", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = Max(upper_index - lower_index, 0); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_list_slice_is_empty); + +Datum age_vle_list_slice_is_empty(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + break; + case 3: + list_count = edge_count; + break; + case 6: + list_count = Max(edge_count - 2, 0); + break; + case 7: + list_count = Max(edge_count - 1, 0); + break; + case 8: + list_count = Max(edge_count - 1, 0); + break; + case 9: + list_count = edge_count; + break; + case 10: + list_count = Max(edge_count - 1, 0); + break; + case 11: + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_list_slice_is_empty: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_vle_list_slice_is_empty", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + PG_RETURN_BOOL(upper_index <= lower_index); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_slice_boundary); + +Datum age_materialize_vle_slice_boundary(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value id_result; + agtype_value label_result; + agtype_value label_array_elem; + agtype_in_state label_array_result; + agtype_value *agtv_result = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + bool return_id = false; + bool return_label = false; + bool return_labels = false; + bool return_properties = false; + bool return_endpoint = false; + bool return_endpoint_id = false; + bool return_endpoint_label = false; + bool return_endpoint_labels = false; + bool return_endpoint_properties = false; + bool start_endpoint = false; + bool double_tail_list = false; + bool tail_reverse_list = false; + bool reverse_list = false; + bool return_node = false; + bool return_last = false; + graphid *graphid_array = NULL; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 base_index = 0; + int64 original_index; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + if (mode >= 360) + { + double_tail_list = true; + mode -= 360; + } + else if (mode >= 240) + { + tail_reverse_list = true; + mode -= 240; + } + else if (mode >= 120) + { + reverse_list = true; + mode -= 120; + } + if (mode >= 8 && mode <= 15) + { + return_id = true; + mode -= 8; + } + else if (mode >= 16 && mode <= 23) + { + return_label = true; + mode -= 16; + } + else if (mode >= 24 && mode <= 31) + { + return_labels = true; + mode -= 24; + } + else if (mode >= 32 && mode <= 39) + { + return_properties = true; + mode -= 32; + } + else if (mode >= 40 && mode <= 47) + { + return_endpoint = true; + start_endpoint = true; + mode -= 40; + } + else if (mode >= 48 && mode <= 55) + { + return_endpoint = true; + start_endpoint = false; + mode -= 48; + } + else if (mode >= 56 && mode <= 63) + { + return_endpoint_id = true; + start_endpoint = true; + mode -= 56; + } + else if (mode >= 64 && mode <= 71) + { + return_endpoint_id = true; + start_endpoint = false; + mode -= 64; + } + else if (mode >= 72 && mode <= 79) + { + return_endpoint_label = true; + start_endpoint = true; + mode -= 72; + } + else if (mode >= 80 && mode <= 87) + { + return_endpoint_label = true; + start_endpoint = false; + mode -= 80; + } + else if (mode >= 88 && mode <= 95) + { + return_endpoint_labels = true; + start_endpoint = true; + mode -= 88; + } + else if (mode >= 96 && mode <= 103) + { + return_endpoint_labels = true; + start_endpoint = false; + mode -= 96; + } + else if (mode >= 104 && mode <= 111) + { + return_endpoint_properties = true; + start_endpoint = true; + mode -= 104; + } + else if (mode >= 112 && mode <= 119) + { + return_endpoint_properties = true; + start_endpoint = false; + mode -= 112; + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + case 1: + list_count = edge_count; + return_node = false; + return_last = mode == 1; + break; + case 2: + case 3: + list_count = edge_count + 1; + return_node = true; + return_last = mode == 3; + break; + case 4: + case 5: + if (double_tail_list) + { + list_count = Max(edge_count - 2, 0); + base_index = 2; + } + else if (tail_reverse_list) + { + list_count = Max(edge_count - 1, 0); + } + else + { + list_count = Max(edge_count - 1, 0); + base_index = 1; + } + return_node = false; + return_last = mode == 5; + break; + case 6: + case 7: + if (double_tail_list) + { + list_count = Max(edge_count - 1, 0); + base_index = 2; + } + else if (tail_reverse_list) + { + list_count = edge_count; + } + else + { + list_count = edge_count; + base_index = 1; + } + return_node = true; + return_last = mode == 7; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_slice_boundary", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + if (upper_index <= lower_index) + { + PG_RETURN_NULL(); + } + + original_index = return_last ? upper_index - 1 : lower_index; + if (reverse_list || tail_reverse_list) + { + original_index = list_count - original_index - 1; + } + original_index += base_index; + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (return_node) + { + graphid node_id = graphid_array[original_index * 2]; + + if (return_endpoint || return_endpoint_id || return_endpoint_label || + return_endpoint_labels || return_endpoint_properties) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: endpoint mode is invalid for nodes"))); + } + + if (return_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = node_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + + if (return_label || return_labels || return_properties) + { + vertex_entry *ve = get_vertex_entry(ggctx, node_id); + char *label_name = NULL; + + Assert(ve != NULL); + if (return_properties) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + if (return_label) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + + label_array_elem = label_result; + MemSet(&label_array_result, 0, sizeof(agtype_in_state)); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_ELEM, + &label_array_elem); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(label_array_result.res)); + } + + agtv_result = build_vle_vertex_value(ggctx, node_id, NULL); + } + else + { + graphid edge_id = graphid_array[(original_index * 2) + 1]; + + if (return_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = edge_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + + if (return_label || return_properties) + { + edge_entry *ee = get_edge_entry(ggctx, edge_id); + char *label_name = NULL; + + Assert(ee != NULL); + if (return_properties) + { + PG_RETURN_DATUM(get_edge_entry_properties(ee)); + } + + label_name = get_edge_entry_label_name(ee); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + if (return_labels) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_slice_boundary: labels mode is invalid for relationships"))); + } + if (return_endpoint || return_endpoint_id || return_endpoint_label || + return_endpoint_labels || return_endpoint_properties) + { + edge_entry *ee = get_edge_entry(ggctx, edge_id); + graphid endpoint_id; + + Assert(ee != NULL); + endpoint_id = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + if (return_endpoint_id) + { + id_result.type = AGTV_INTEGER; + id_result.val.int_value = endpoint_id; + PG_RETURN_POINTER(agtype_value_to_agtype(&id_result)); + } + if (return_endpoint) + { + agtv_result = build_vle_vertex_value(ggctx, endpoint_id, NULL); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); + } + if (return_endpoint_label || return_endpoint_labels || + return_endpoint_properties) + { + vertex_entry *ve = get_vertex_entry(ggctx, endpoint_id); + char *label_name = NULL; + + Assert(ve != NULL); + if (return_endpoint_properties) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + label_result.type = AGTV_STRING; + label_result.val.string.val = label_name; + label_result.val.string.len = strlen(label_name); + if (return_endpoint_label) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&label_result)); + } + + label_array_elem = label_result; + MemSet(&label_array_result, 0, sizeof(agtype_in_state)); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_BEGIN_ARRAY, NULL); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_ELEM, + &label_array_elem); + label_array_result.res = push_agtype_value( + &label_array_result.parse_state, WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER( + agtype_value_to_agtype(label_array_result.res)); + } + } + + agtv_result = build_vle_edge_value(ggctx, edge_id, NULL); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_at); + +Datum age_materialize_vle_node_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_node_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_vertex = agtv_materialize_vle_vertex_at(agt_arg_vpc, node_index); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_reversed_at); + +Datum age_materialize_vle_node_reversed_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_node_reversed_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + if (node_index == PG_INT64_MIN) + { + PG_RETURN_NULL(); + } + + agtv_vertex = agtv_materialize_vle_vertex_at( + agt_arg_vpc, -node_index - 1); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_node_tail_last); + +Datum age_materialize_vle_node_tail_last(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + graphid *graphid_array = NULL; + int64 node_count; + agtype_value *agtv_vertex = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + agtv_vertex = build_vle_vertex_value( + ggctx, graphid_array[(node_count - 1) * 2], NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_tail_last_id); + +Datum age_vle_node_tail_last_id(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + graphid *graphid_array = NULL; + int64 node_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[(node_count - 1) * 2]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_id_at); + +Datum age_vle_node_id_at(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + int64 node_count; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[node_index * 2]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_label_at); + +Datum age_vle_node_label_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_label_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + label_name = get_vertex_entry_label_name(ve); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_labels_at); + +Datum age_vle_node_labels_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_label; + agtype_in_state agis_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_labels_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, WAGT_ELEM, + &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); +} + +PG_FUNCTION_INFO_V1(age_vle_node_properties_at); + +Datum age_vle_node_properties_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid node_id; + int64 node_count; + int64 node_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_node_properties_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + node_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + node_count = (vpc->graphid_array_size + 1) / 2; + if (node_index < 0) + { + node_index = node_count + node_index; + } + if (node_index < 0 || node_index >= node_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + node_id = graphid_array[node_index * 2]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, node_id); + Assert(ve != NULL); + + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_slice); + +Datum age_materialize_vle_nodes_slice(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value *lidx_value_ptr = NULL; + agtype_value *uidx_value_ptr = NULL; + agtype_value *agtv_array = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + int64 node_count; + int64 lower_index = 0; + int64 upper_index = 0; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + node_count = agtv_vle_node_count(agt_arg_vpc); + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_nodes_slice", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + lidx_value_ptr = &lidx_value; + if (lidx_value_ptr->type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + PG_RETURN_NULL(); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = node_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_nodes_slice", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + uidx_value_ptr = &uidx_value; + if (uidx_value_ptr->type == AGTV_NULL) + { + if (lidx_value_ptr != NULL && lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + if (uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + PG_RETURN_NULL(); + } + } + + if ((lidx_value_ptr != NULL && lidx_value_ptr->type != AGTV_INTEGER) || + (uidx_value_ptr != NULL && uidx_value_ptr->type != AGTV_INTEGER)) + { + if (lidx_value_ptr != NULL && lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + if (uidx_value_ptr != NULL && uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + + if (lidx_value_ptr != NULL) + { + lower_index = lidx_value_ptr->val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(lidx_value_ptr); + } + } + if (uidx_value_ptr != NULL) + { + upper_index = uidx_value_ptr->val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(uidx_value_ptr); + } + } + + if (lower_index < 0) + { + lower_index = node_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > node_count) + { + lower_index = node_count; + } + if (upper_index < 0) + { + upper_index = node_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > node_count) + { + upper_index = node_count; + } + + if (upper_index <= lower_index) + { + PG_FREE_IF_COPY(agt_arg_vpc, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_nodes_slice( + agt_arg_vpc, lower_index, upper_index); + + PG_FREE_IF_COPY(agt_arg_vpc, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_list_slice); + +Datum age_materialize_vle_list_slice(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype *agt_lidx = NULL; + agtype *agt_uidx = NULL; + agtype_value lidx_value; + agtype_value uidx_value; + agtype_value mode_value; + agtype_value *agtv_array = NULL; + bool lidx_needs_free = false; + bool uidx_needs_free = false; + bool mode_needs_free = false; + bool reverse = false; + bool node_list = false; + int64 edge_count; + int64 list_count; + int64 lower_index = 0; + int64 upper_index = 0; + int64 base_index = 0; + int64 reverse_base_index = 0; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(3)) + { + PG_RETURN_NULL(); + } + + if (PG_ARGISNULL(1) && PG_ARGISNULL(2)) + { + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("slice start and/or end is required"))); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + AG_GET_ARG_AGTYPE_P(3), AGTV_INTEGER, true, + &mode_value, &mode_needs_free); + mode = mode_value.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&mode_value); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + switch (mode) + { + case 0: + list_count = edge_count; + break; + case 1: + node_list = true; + list_count = edge_count + 1; + break; + case 2: + list_count = Max(edge_count - 1, 0); + base_index = 1; + break; + case 3: + node_list = true; + list_count = edge_count; + base_index = 1; + break; + case 4: + reverse = true; + list_count = edge_count; + break; + case 5: + reverse = true; + node_list = true; + list_count = edge_count + 1; + break; + case 6: + list_count = Max(edge_count - 2, 0); + base_index = 2; + break; + case 7: + node_list = true; + list_count = Max(edge_count - 1, 0); + base_index = 2; + break; + case 8: + reverse = true; + list_count = Max(edge_count - 1, 0); + reverse_base_index = 1; + break; + case 9: + reverse = true; + node_list = true; + list_count = edge_count; + reverse_base_index = 1; + break; + case 10: + reverse = true; + list_count = Max(edge_count - 1, 0); + break; + case 11: + reverse = true; + node_list = true; + list_count = edge_count; + break; + default: + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_materialize_vle_list_slice: invalid mode"))); + } + + if (PG_ARGISNULL(1)) + { + lower_index = 0; + } + else + { + agt_lidx = AG_GET_ARG_AGTYPE_P(1); + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + agt_lidx, AGTV_INTEGER, false, + &lidx_value, &lidx_needs_free); + if (lidx_value.type == AGTV_NULL) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + PG_RETURN_NULL(); + } + if (lidx_value.type != AGTV_INTEGER) + { + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + lower_index = lidx_value.val.int_value; + if (lidx_needs_free) + { + pfree_agtype_value_content(&lidx_value); + } + } + + if (PG_ARGISNULL(2)) + { + upper_index = list_count; + } + else + { + agt_uidx = AG_GET_ARG_AGTYPE_P(2); + get_vle_scalar_arg_no_copy("age_materialize_vle_list_slice", + agt_uidx, AGTV_INTEGER, false, + &uidx_value, &uidx_needs_free); + if (uidx_value.type == AGTV_NULL) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + PG_RETURN_NULL(); + } + if (uidx_value.type != AGTV_INTEGER) + { + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + ereport(ERROR, + (errmsg("array slices must resolve to an integer value"))); + } + upper_index = uidx_value.val.int_value; + if (uidx_needs_free) + { + pfree_agtype_value_content(&uidx_value); + } + } + + if (lower_index < 0) + { + lower_index = list_count + lower_index; + } + if (lower_index < 0) + { + lower_index = 0; + } + if (lower_index > list_count) + { + lower_index = list_count; + } + if (upper_index < 0) + { + upper_index = list_count + upper_index; + } + if (upper_index < 0) + { + upper_index = 0; + } + if (upper_index > list_count) + { + upper_index = list_count; + } + + if (upper_index <= lower_index) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + if (reverse) + { + lower_index += reverse_base_index; + upper_index += reverse_base_index; + agtv_array = node_list ? + agtv_materialize_vle_nodes_reversed_slice(agt_arg_vpc, + lower_index, + upper_index) : + agtv_materialize_vle_edges_reversed_slice(agt_arg_vpc, + lower_index, + upper_index); + } + else + { + lower_index += base_index; + upper_index += base_index; + agtv_array = node_list ? + agtv_materialize_vle_nodes_slice(agt_arg_vpc, lower_index, + upper_index) : + agtv_materialize_vle_edges_slice(agt_arg_vpc, lower_index, + upper_index); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_tail); + +Datum age_materialize_vle_nodes_tail(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 node_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_nodes_slice( + agt_arg_vpc, Min(node_count, 1), node_count); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_nodes_reversed); + +Datum age_materialize_vle_nodes_reversed(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_array = agtv_materialize_vle_nodes_reversed(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_at); + +Datum age_materialize_vle_edge_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_edge = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_edge_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_edge = agtv_materialize_vle_edge_at(agt_arg_vpc, edge_index); + if (agtv_edge == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_reversed_at); + +Datum age_materialize_vle_edge_reversed_at(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_edge = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_materialize_vle_edge_reversed_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + if (edge_index == PG_INT64_MIN) + { + PG_RETURN_NULL(); + } + + agtv_edge = agtv_materialize_vle_edge_at(agt_arg_vpc, -edge_index - 1); + if (agtv_edge == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edge_tail_last); + +Datum age_materialize_vle_edge_tail_last(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_edge = NULL; + graphid *graphid_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_edge = build_vle_edge_value(ggctx, graphid_array[edge_count * 2 - 1], + NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_edge)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_tail_last_id); + +Datum age_vle_edge_tail_last_id(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + graphid *graphid_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[edge_count * 2 - 1]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_field); + +Datum age_vle_tail_last_field(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + vertex_entry *ve = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_result; + agtype_value agtv_label; + agtype_in_state agis_result; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid entity_id; + int64 node_count; + int64 edge_count; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_field", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + + if (mode >= 0 && mode <= 2) + { + node_count = agtv_vle_node_count(agt_arg_vpc); + if (node_count <= 1) + { + PG_RETURN_NULL(); + } + + entity_id = graphid_array[(node_count - 1) * 2]; + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ve = get_vertex_entry(ggctx, entity_id); + Assert(ve != NULL); + + if (mode == 2) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + if (mode == 0) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); + } + + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + } + + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + if (mode < 3 || mode > 4) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_field: invalid mode"))); + } + + entity_id = graphid_array[edge_count * 2 - 1]; + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, entity_id); + Assert(ee != NULL); + + if (mode == 3) + { + label_name = get_edge_entry_label_name(ee); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + else if (mode == 4) + { + PG_RETURN_DATUM(get_edge_entry_properties(ee)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_edge_endpoint); + +Datum age_vle_tail_last_edge_endpoint(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_result; + agtype_value *agtv_vertex = NULL; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 mode; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_edge_endpoint", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[edge_count * 2 - 1]; + + if (mode < 0 || mode > 3) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_edge_endpoint: invalid mode"))); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = (mode == 0 || mode == 2) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + + if (mode == 0 || mode == 1) + { + agtv_vertex = build_vle_vertex_value(ggctx, endpoint_id, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); + } + else if (mode == 2 || mode == 3) + { + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = endpoint_id; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_tail_last_endpoint_field); + +Datum age_vle_tail_last_endpoint_field(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_mode; + agtype_value agtv_label; + agtype_in_state agis_result; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_tail_last_endpoint_field", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + mode = agtv_mode.val.int_value; + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[edge_count * 2 - 1]; + + if (mode < 0 || mode > 5) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_tail_last_endpoint_field: invalid mode"))); + } + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + endpoint_id = (mode % 2 == 0) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + ve = get_vertex_entry(ggctx, endpoint_id); + Assert(ve != NULL); + + if (mode == 4 || mode == 5) + { + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } + + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); + + if (mode == 0 || mode == 1) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); + } + else if (mode == 2 || mode == 3) + { + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); + } + + Assert(false); + PG_RETURN_NULL(); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_id_at); + +Datum age_vle_edge_id_at(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + int64 edge_count; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = graphid_array[(edge_index * 2) + 1]; + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_index_exists); + +Datum age_vle_edge_index_exists(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + int64 edge_index; + graphid edge_id; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_index_exists", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_index, &index_needs_free); + + if (agtv_index.type == AGTV_NULL) + { + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + PG_RETURN_NULL(); + } + if (agtv_index.type != AGTV_INTEGER) + { + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_index_exists: index must resolve to an integer"))); + } + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (!get_vle_edge_id_at_index(agt_arg_vpc, edge_index, &edge_id)) + { + PG_RETURN_NULL(); + } + + PG_RETURN_DATUM(boolean_to_agtype(true)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_indices_equal); + +Datum age_vle_edge_indices_equal(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_left_index; + agtype_value agtv_right_index; + bool left_index_needs_free = false; + bool right_index_needs_free = false; + graphid left_edge_id; + graphid right_edge_id; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_indices_equal", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_left_index, &left_index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_indices_equal", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, false, + &agtv_right_index, &right_index_needs_free); + + if (agtv_left_index.type == AGTV_NULL || + agtv_right_index.type == AGTV_NULL) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + PG_RETURN_NULL(); + } + if (agtv_left_index.type != AGTV_INTEGER || + agtv_right_index.type != AGTV_INTEGER) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_indices_equal: indexes must resolve to integers"))); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + if (!get_vle_edge_id_at_index(agt_arg_vpc, + agtv_left_index.val.int_value, + &left_edge_id) || + !get_vle_edge_id_at_index(agt_arg_vpc, + agtv_right_index.val.int_value, + &right_edge_id)) + { + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + PG_RETURN_NULL(); + } + + result = left_edge_id == right_edge_id; + + if (left_index_needs_free) + { + pfree_agtype_value_content(&agtv_left_index); + } + if (right_index_needs_free) + { + pfree_agtype_value_content(&agtv_right_index); + } + + PG_RETURN_DATUM(boolean_to_agtype(result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_reversed_index_equal); + +Datum age_vle_edge_reversed_index_equal(PG_FUNCTION_ARGS) +{ + VLE_path_container *vpc = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_reversed_index; + agtype_value agtv_normal_index; + bool reversed_index_needs_free = false; + bool normal_index_needs_free = false; + graphid reversed_edge_id; + graphid normal_edge_id; + int64 edge_count; + int64 reversed_index; + bool result; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_reversed_index_equal", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, false, + &agtv_reversed_index, + &reversed_index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_reversed_index_equal", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, false, + &agtv_normal_index, &normal_index_needs_free); + + if (agtv_reversed_index.type == AGTV_NULL || + agtv_normal_index.type == AGTV_NULL) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + PG_RETURN_NULL(); + } + if (agtv_reversed_index.type != AGTV_INTEGER || + agtv_normal_index.type != AGTV_INTEGER) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("age_vle_edge_reversed_index_equal: indexes must resolve to integers"))); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + reversed_index = agtv_reversed_index.val.int_value; + if (reversed_index < 0) + { + reversed_index = edge_count + reversed_index; + } + if (reversed_index < 0 || reversed_index >= edge_count || + !get_vle_edge_id_at_index(agt_arg_vpc, + edge_count - reversed_index - 1, + &reversed_edge_id) || + !get_vle_edge_id_at_index(agt_arg_vpc, + agtv_normal_index.val.int_value, + &normal_edge_id)) + { + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + PG_RETURN_NULL(); + } + + result = reversed_edge_id == normal_edge_id; + + if (reversed_index_needs_free) + { + pfree_agtype_value_content(&agtv_reversed_index); + } + if (normal_index_needs_free) + { + pfree_agtype_value_content(&agtv_normal_index); + } + + PG_RETURN_DATUM(boolean_to_agtype(result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_label_at); + +Datum age_vle_edge_label_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_label_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + label_name = get_edge_entry_label_name(ee); + agtv_result.type = AGTV_STRING; + agtv_result.val.string.val = label_name; + agtv_result.val.string.len = strlen(label_name); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_properties_at); + +Datum age_vle_edge_properties_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_properties_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); + + PG_RETURN_DATUM(get_edge_entry_properties(ee)); +} + +static Datum age_vle_edge_endpoint_at(FunctionCallInfo fcinfo, + bool start_endpoint) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value *agtv_vertex = NULL; + bool index_needs_free = false; + int64 edge_index; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy(start_endpoint ? + "age_vle_edge_start_node_at" : + "age_vle_edge_end_node_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + + agtv_vertex = agtv_materialize_vle_edge_endpoint_at( + agt_arg_vpc, edge_index, start_endpoint); + if (agtv_vertex == NULL) + { + PG_RETURN_NULL(); + } + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_vertex)); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_start_node_at); + +Datum age_vle_edge_start_node_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_at(fcinfo, true); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_end_node_at); + +Datum age_vle_edge_end_node_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_at(fcinfo, false); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_endpoint_field_at); + +Datum age_vle_edge_endpoint_field_at(PG_FUNCTION_ARGS) +{ + GRAPH_global_context *ggctx = NULL; + VLE_path_container *vpc = NULL; + edge_entry *ee = NULL; + vertex_entry *ve = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_mode; + agtype_value agtv_label; + agtype_in_state agis_result; + bool index_needs_free = false; + bool mode_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + graphid endpoint_id; + int64 edge_count; + int64 edge_index; + int64 mode; + char *label_name = NULL; + + if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy("age_vle_edge_endpoint_field_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + get_vle_scalar_arg_no_copy("age_vle_edge_endpoint_field_at", + AG_GET_ARG_AGTYPE_P(2), AGTV_INTEGER, true, + &agtv_mode, &mode_needs_free); + + edge_index = agtv_index.val.int_value; + mode = agtv_mode.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } + if (mode_needs_free) + { + pfree_agtype_value_content(&agtv_mode); + } + + Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); + Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); + + vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) { - gid = DATUM_GET_GRAPHID(PG_GETARG_DATUM(1)); + PG_RETURN_NULL(); } - else + + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; + + if (mode < 0 || mode > 5) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("match_vle_terminal_edge() argument 1 must be an agtype integer or a graphid"))); + errmsg("age_vle_edge_endpoint_field_at: invalid mode"))); } - pos_agt = AG_GET_ARG_AGTYPE_P(2); + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); - vle_is_on_left = get_agtype_scalar_bool_arg(pos_agt, - "argument 3 of age_match_vle_edge_to_edge_qual must be an integer"); + endpoint_id = (mode % 2 == 0) ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + ve = get_vertex_entry(ggctx, endpoint_id); + Assert(ve != NULL); - if (vle_is_on_left) + if (mode == 4 || mode == 5) { - int array_size = vle_path->graphid_array_size; + PG_RETURN_DATUM(get_vertex_entry_properties(ve)); + } - /* - * Path is like ...[vle_edge]-()-[regular_edge]... Get the graphid of - * the vertex at the endof the path and check that it matches the id - * that was passed in the second arg. The transform logic is responsible - * for making that the start or end id, depending on its direction. - */ - if (gid != array[array_size - 1]) - { - PG_RETURN_BOOL(false); - } + label_name = get_vertex_entry_label_name(ve); + agtv_label.type = AGTV_STRING; + agtv_label.val.string.val = label_name; + agtv_label.val.string.len = strlen(label_name); - PG_RETURN_BOOL(true); + if (mode == 0 || mode == 1) + { + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_label)); } - else + else if (mode == 2 || mode == 3) { - /* - * Path is like ...[edge]-()-[vle_edge]... Get the vertex at the start - * of the vle edge and check against id. - */ - if (gid != array[0]) - { - PG_RETURN_BOOL(false); - } - - PG_RETURN_BOOL(true); + MemSet(&agis_result, 0, sizeof(agtype_in_state)); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_BEGIN_ARRAY, NULL); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_ELEM, &agtv_label); + agis_result.res = push_agtype_value(&agis_result.parse_state, + WAGT_END_ARRAY, NULL); + + PG_RETURN_POINTER(agtype_value_to_agtype(agis_result.res)); } + + Assert(false); + PG_RETURN_NULL(); } -/* - * Exposed helper function to make an agtype_value AGTV_ARRAY of edges from a - * VLE_path_container. - */ -agtype_value *agtv_materialize_vle_edges(agtype *agt_arg_vpc) +static Datum age_vle_edge_endpoint_id_at(FunctionCallInfo fcinfo, + bool start_endpoint) { + GRAPH_global_context *ggctx = NULL; VLE_path_container *vpc = NULL; - agtype_value *agtv_array = NULL; + edge_entry *ee = NULL; + agtype *agt_arg_vpc = NULL; + agtype_value agtv_index; + agtype_value agtv_result; + bool index_needs_free = false; + graphid *graphid_array = NULL; + graphid edge_id; + int64 edge_count; + int64 edge_index; - /* the passed argument should not be NULL */ - Assert(agt_arg_vpc != NULL); + if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + get_vle_scalar_arg_no_copy(start_endpoint ? + "age_vle_edge_start_id_at" : + "age_vle_edge_end_id_at", + AG_GET_ARG_AGTYPE_P(1), AGTV_INTEGER, true, + &agtv_index, &index_needs_free); + + edge_index = agtv_index.val.int_value; + if (index_needs_free) + { + pfree_agtype_value_content(&agtv_index); + } - /* - * The path must be a binary container and the type of the object in the - * container must be an AGT_FBINARY_TYPE_VLE_PATH. - */ Assert(AGT_ROOT_IS_BINARY(agt_arg_vpc)); Assert(AGT_ROOT_BINARY_FLAGS(agt_arg_vpc) == AGT_FBINARY_TYPE_VLE_PATH); - /* get the container */ vpc = (VLE_path_container *)agt_arg_vpc; + edge_count = (vpc->graphid_array_size - 1) / 2; + if (edge_index < 0) + { + edge_index = edge_count + edge_index; + } + if (edge_index < 0 || edge_index >= edge_count) + { + PG_RETURN_NULL(); + } - /* it should not be null */ - Assert(vpc != NULL); + graphid_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(vpc); + edge_id = graphid_array[(edge_index * 2) + 1]; - /* build the AGTV_ARRAY of edges from the VLE_path_container */ - agtv_array = build_edge_list(vpc); + ggctx = find_GRAPH_global_context(vpc->graph_oid); + Assert(ggctx != NULL); + ee = get_edge_entry(ggctx, edge_id); + Assert(ee != NULL); - return agtv_array; + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = start_endpoint ? + get_edge_entry_start_vertex_id(ee) : + get_edge_entry_end_vertex_id(ee); + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); } -/* PG wrapper function for agtv_materialize_vle_edges */ -PG_FUNCTION_INFO_V1(age_materialize_vle_edges); +PG_FUNCTION_INFO_V1(age_vle_edge_start_id_at); -Datum age_materialize_vle_edges(PG_FUNCTION_ARGS) +Datum age_vle_edge_start_id_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_id_at(fcinfo, true); +} + +PG_FUNCTION_INFO_V1(age_vle_edge_end_id_at); + +Datum age_vle_edge_end_id_at(PG_FUNCTION_ARGS) +{ + return age_vle_edge_endpoint_id_at(fcinfo, false); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edges_tail); + +Datum age_materialize_vle_edges_tail(PG_FUNCTION_ARGS) { agtype *agt_arg_vpc = NULL; agtype_value *agtv_array = NULL; + int64 edge_count; - /* if we have a NULL VLE_path_container, return NULL */ if (PG_ARGISNULL(0)) { PG_RETURN_NULL(); } - /* get the VLE_path_container argument */ agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } - /* if NULL, return NULL */ + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges_slice( + agt_arg_vpc, Min(edge_count, 1), edge_count); + + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); +} + +PG_FUNCTION_INFO_V1(age_materialize_vle_edges_reversed); + +Datum age_materialize_vle_edges_reversed(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value *agtv_array = NULL; + int64 edge_count; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); if (is_agtype_null(agt_arg_vpc)) { PG_RETURN_NULL(); } - agtv_array = agtv_materialize_vle_edges(agt_arg_vpc); + edge_count = agtv_vle_edge_count(agt_arg_vpc); + if (edge_count == 0) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agtv_array = agtv_materialize_vle_edges_reversed(agt_arg_vpc); PG_RETURN_POINTER(agtype_value_to_agtype(agtv_array)); } @@ -2621,6 +7119,30 @@ Datum age_materialize_vle_path(PG_FUNCTION_ARGS) PG_RETURN_POINTER(agt_materialize_vle_path(agt_arg_vpc)); } +PG_FUNCTION_INFO_V1(age_vle_path_length); + +Datum age_vle_path_length(PG_FUNCTION_ARGS) +{ + agtype *agt_arg_vpc = NULL; + agtype_value agtv_result; + + if (PG_ARGISNULL(0)) + { + PG_RETURN_NULL(); + } + + agt_arg_vpc = AG_GET_ARG_AGTYPE_P(0); + if (is_agtype_null(agt_arg_vpc)) + { + PG_RETURN_NULL(); + } + + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_edge_count(agt_arg_vpc); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); +} + /* * PG function to take a VLE_path_container and return whether the supplied end * vertex (target/veid) matches against the last edge in the VLE path. The VLE @@ -2973,6 +7495,197 @@ static Oid get_cached_edge_uniqueness_argtype(FunctionCallInfo fcinfo, return cache->types[argno]; } +static bool get_edge_uniqueness_vle_arg(Datum arg, Oid type, + VLE_path_container **vpc) +{ + agtype *agt = NULL; + + if (type != AGTYPEOID) + { + return false; + } + + agt = DATUM_GET_AGTYPE_P(arg); + if (!AGT_ROOT_IS_BINARY(agt) || + AGT_ROOT_BINARY_FLAGS(agt) != AGT_FBINARY_TYPE_VLE_PATH) + { + return false; + } + + *vpc = (VLE_path_container *)agt; + return true; +} + +static bool get_edge_uniqueness_fixed_edge_id(Datum arg, Oid type, int argno, + graphid *edge_id) +{ + agtype *agt = NULL; + agtype_value agtv_id; + bool id_needs_free = false; + bool id_found; + + if (type == INT8OID || type == GRAPHIDOID) + { + *edge_id = DatumGetInt64(arg); + return true; + } + if (type != AGTYPEOID) + { + return false; + } + + agt = DATUM_GET_AGTYPE_P(arg); + if (AGT_ROOT_IS_BINARY(agt) && + AGT_ROOT_BINARY_FLAGS(agt) == AGT_FBINARY_TYPE_VLE_PATH) + { + return false; + } + if (!AGT_ROOT_IS_SCALAR(agt)) + { + return false; + } + + id_found = get_ith_agtype_value_from_container_no_copy( + &agt->root, 0, &agtv_id, &id_needs_free); + Assert(id_found); + + if (agtv_id.type != AGTV_INTEGER) + { + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("_ag_enforce_edge_uniqueness parameter %d must resolve to an agtype integer", + argno))); + } + + *edge_id = agtv_id.val.int_value; + if (id_needs_free) + { + pfree_agtype_value_content(&agtv_id); + } + + return true; +} + +static bool vle_path_contains_edge_id(VLE_path_container *vpc, graphid edge_id) +{ + return agt_vle_contains_edge_id((agtype *)vpc, edge_id); +} + +static int64 get_vle_path_edge_count(VLE_path_container *vpc) +{ + return (vpc->graphid_array_size - 1) / 2; +} + +static bool vle_paths_have_common_edge(VLE_path_container *left_vpc, + int64 left_edge_count, + VLE_path_container *right_vpc, + int64 right_edge_count) +{ + VLE_path_container *outer_vpc = left_vpc; + VLE_path_container *inner_vpc = right_vpc; + graphid *outer_array = NULL; + graphid *inner_array = NULL; + int64 outer_edge_count = left_edge_count; + int64 inner_edge_count = right_edge_count; + int64 i; + int64 j; + + if (outer_edge_count > inner_edge_count) + { + outer_vpc = right_vpc; + inner_vpc = left_vpc; + outer_edge_count = right_edge_count; + inner_edge_count = left_edge_count; + } + + outer_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(outer_vpc); + inner_array = GET_GRAPHID_ARRAY_FROM_CONTAINER(inner_vpc); + + for (i = 0; i < outer_edge_count; i++) + { + graphid edge_id = outer_array[(i * 2) + 1]; + + for (j = 0; j < inner_edge_count; j++) + { + if (edge_id == inner_array[(j * 2) + 1]) + { + return true; + } + } + } + + return false; +} + +static bool try_enforce_two_vle_uniqueness(Datum *args, Oid *types, + int nargs, bool *result) +{ + VLE_path_container *left_vpc = NULL; + VLE_path_container *right_vpc = NULL; + int64 left_edge_count; + int64 right_edge_count; + + if (nargs != 2) + { + return false; + } + + if (!get_edge_uniqueness_vle_arg(args[0], types[0], &left_vpc) || + !get_edge_uniqueness_vle_arg(args[1], types[1], &right_vpc)) + { + return false; + } + + left_edge_count = get_vle_path_edge_count(left_vpc); + right_edge_count = get_vle_path_edge_count(right_vpc); + if (left_edge_count == 0 || right_edge_count == 0) + { + *result = true; + return true; + } + + if (left_edge_count * right_edge_count > EDGE_UNIQUENESS_NESTED_SCAN_LIMIT) + { + return false; + } + + *result = !vle_paths_have_common_edge(left_vpc, left_edge_count, + right_vpc, right_edge_count); + return true; +} + +static bool try_enforce_one_vle_one_edge_uniqueness(Datum *args, Oid *types, + int nargs, bool *result) +{ + VLE_path_container *vpc = NULL; + graphid edge_id = 0; + + if (nargs != 2) + { + return false; + } + + if (get_edge_uniqueness_vle_arg(args[0], types[0], &vpc) && + get_edge_uniqueness_fixed_edge_id(args[1], types[1], 1, &edge_id)) + { + *result = !vle_path_contains_edge_id(vpc, edge_id); + return true; + } + + if (get_edge_uniqueness_vle_arg(args[1], types[1], &vpc) && + get_edge_uniqueness_fixed_edge_id(args[0], types[0], 0, &edge_id)) + { + *result = !vle_path_contains_edge_id(vpc, edge_id); + return true; + } + + return false; +} + /* * This function checks the edges in a MATCH clause to see if they are unique or * not. Filters out all the paths where the edge uniques rules are not met. @@ -2993,6 +7706,7 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) int nargs = 0; int64 estimated_edges = 0; int i = 0; + bool fast_result = false; /* extract our arguments */ nargs = get_edge_uniqueness_args_fast(fcinfo, &args, &types, &nulls, @@ -3017,7 +7731,20 @@ Datum _ag_enforce_edge_uniqueness(PG_FUNCTION_ARGS) errmsg("_ag_enforce_edge_uniqueness argument %d must be AGTYPE, INT8, or GRAPHIDOID", i))); } + } + + if (try_enforce_one_vle_one_edge_uniqueness(args, types, nargs, + &fast_result)) + { + PG_RETURN_BOOL(fast_result); + } + if (try_enforce_two_vle_uniqueness(args, types, nargs, &fast_result)) + { + PG_RETURN_BOOL(fast_result); + } + for (i = 0; i < nargs; i++) + { if (types[i] == INT8OID || types[i] == GRAPHIDOID) { estimated_edges++; diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c index d4c7cc91d..04be6064a 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -188,6 +188,7 @@ static agtype_value *execute_map_access_operator_internal(agtype *map, agtype_value *map_value, char *key, int key_len); +static agtype *build_empty_agtype_array(void); static Datum agtype_object_field_impl(FunctionCallInfo fcinfo, agtype *agtype_in, char *key, int key_len, bool as_text); @@ -2544,16 +2545,7 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) AGT_ROOT_IS_BINARY(agt) && AGT_ROOT_BINARY_FLAGS(agt) == AGT_FBINARY_TYPE_VLE_PATH) { - agtype_value *agtv_path = NULL; - int j = 0; - - /* get the VLE path from the container as an agtype_value */ - agtv_path = agtv_materialize_vle_path(agt); - - PG_FREE_IF_COPY(agt, i); - - /* it better be an AGTV_PATH */ - Assert(agtv_path->type == AGTV_PATH); + int64 appended; /* * If the VLE path is the zero boundary case, there isn't an edge to @@ -2561,21 +2553,11 @@ Datum _agtype_build_path(PG_FUNCTION_ARGS) * We need to flag this condition so that we can skip processing the * following vertex. */ - if (agtv_path->val.array.num_elems == 1) + appended = agt_vle_append_path_interior(agt, &result); + PG_FREE_IF_COPY(agt, i); + if (appended == 0) { is_zero_boundary_case = true; - continue; - } - - /* - * Add in the interior path - excluding the start and end vertices. - * The other iterations of the for loop has handled start and will - * handle end. - */ - for (j = 1; j <= agtv_path->val.array.num_elems - 2; j++) - { - result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, - &agtv_path->val.array.elems[j]); } } else if (i % 2 == 1 && (!AGTE_IS_AGTYPE(agt->root.children[0]) || @@ -3090,6 +3072,23 @@ Datum agtype_build_map_nonull(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(agtype_build_list); +static agtype *build_empty_agtype_array(void) +{ + agtype_in_state result; + agtype *agt_result; + + memset(&result, 0, sizeof(agtype_in_state)); + + result.res = push_agtype_value(&result.parse_state, WAGT_BEGIN_ARRAY, + NULL); + result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agt_result = agtype_value_to_agtype(result.res); + + pfree_agtype_in_state(&result); + + return agt_result; +} + /* * SQL function agtype_build_list(variadic "any") */ @@ -4711,6 +4710,7 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) agtype *result = NULL; int i = 0; bool using_fast_args = false; + bool container_is_vpc = false; /* * Fast path for the common 2-argument case (object.property or @@ -4721,6 +4721,7 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) if (PG_NARGS() == 2) { agtype *key = NULL; + agtype *vpc_container = NULL; /* check for NULLs */ if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) @@ -4736,7 +4737,8 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) { if (AGT_ROOT_BINARY_FLAGS(container) == AGT_FBINARY_TYPE_VLE_PATH) { - container_value = agtv_materialize_vle_edges(container); + vpc_container = container; + container_is_vpc = true; container = NULL; } else @@ -4773,6 +4775,38 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) errmsg("key must resolve to a scalar value"))); } + if (container_is_vpc) + { + agtype_value key_value; + bool key_needs_free = false; + + (void)get_ith_agtype_value_from_container_no_copy( + &key->root, 0, &key_value, &key_needs_free); + + if (key_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&key_value, key_needs_free); + PG_RETURN_NULL(); + } + if (key_value.type != AGTV_INTEGER) + { + ereport(ERROR, + (errmsg("array index must resolve to an integer value"))); + } + + container_value = agtv_materialize_vle_edge_at( + vpc_container, key_value.val.int_value); + free_agtype_value_no_copy(&key_value, key_needs_free); + + if (container_value == NULL || container_value->type == AGTV_NULL) + { + PG_RETURN_NULL(); + } + + result = agtype_value_to_agtype(container_value); + return AGTYPE_P_GET_DATUM(result); + } + /* extract properties from vertex/edge */ if (container_value != NULL && (container_value->type == AGTV_EDGE || @@ -4892,17 +4926,14 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) /* get the container argument. It could be an object or array */ container = DATUM_GET_AGTYPE_P(args[0]); + container_is_vpc = false; /* if it is a binary container, check for a VLE vpc */ if (AGT_ROOT_IS_BINARY(container)) { if (AGT_ROOT_BINARY_FLAGS(container) == AGT_FBINARY_TYPE_VLE_PATH) { - /* retrieve an array of edges from the vpc */ - container_value = agtv_materialize_vle_edges(container); - /* clear the container reference */ - - container = NULL; + container_is_vpc = true; } else { @@ -4946,6 +4977,72 @@ Datum agtype_access_operator(PG_FUNCTION_ARGS) errmsg("key must resolve to a scalar value"))); } + if (container_is_vpc) + { + agtype_value key_value; + bool key_needs_free = false; + int64 edge_index; + + (void)get_ith_agtype_value_from_container_no_copy( + &key->root, 0, &key_value, &key_needs_free); + + if (key_value.type == AGTV_NULL) + { + free_agtype_value_no_copy(&key_value, key_needs_free); + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + if (key_value.type != AGTV_INTEGER) + { + ereport(ERROR, + (errmsg("array index must resolve to an integer value"))); + } + + edge_index = key_value.val.int_value; + free_agtype_value_no_copy(&key_value, key_needs_free); + + if (i + 1 < nargs) + { + container = agt_vle_edge_properties_at(container, edge_index); + if (container == NULL) + { + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + + container_is_vpc = false; + container_value = NULL; + continue; + } + + container_value = agtv_materialize_vle_edge_at(container, + edge_index); + if (container_value == NULL || container_value->type == AGTV_NULL) + { + if (!using_fast_args) + { + pfree_if_not_null(args); + pfree_if_not_null(types); + pfree_if_not_null(nulls); + } + PG_RETURN_NULL(); + } + + container_is_vpc = false; + container = NULL; + continue; + } + /* * Check for a vertex or edge container_value and extract the properties * object. @@ -5051,6 +5148,7 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) int64 lower_index = 0; uint32 array_size = 0; int64 i = 0; + bool is_vpc = false; /* return null if the array to slice is null */ if (PG_ARGISNULL(0)) @@ -5067,20 +5165,18 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) /* get the array parameter and verify that it is a list */ agt_array = AG_GET_ARG_AGTYPE_P(0); + is_vpc = AGT_ROOT_IS_VPC(agt_array); - if ((!AGT_ROOT_IS_ARRAY(agt_array) && !AGT_ROOT_IS_VPC(agt_array)) || AGT_ROOT_IS_SCALAR(agt_array)) + if ((!AGT_ROOT_IS_ARRAY(agt_array) && !is_vpc) || + AGT_ROOT_IS_SCALAR(agt_array)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("slice must access a list"))); } - /* If we have a vpc, decode it and get AGTV_ARRAY agtype_value */ - if (AGT_ROOT_IS_VPC(agt_array)) + if (is_vpc) { - agtv_array = agtv_materialize_vle_edges(agt_array); - - /* get the size of array */ - array_size = agtv_array->val.array.num_elems; + array_size = (uint32)agtv_vle_edge_count(agt_array); } else { @@ -5185,6 +5281,30 @@ Datum agtype_access_slice(PG_FUNCTION_ARGS) upper_index = array_size; } + if (lower_index >= upper_index) + { + agt_result = build_empty_agtype_array(); + + PG_FREE_IF_COPY(agt_array, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agt_result); + } + + if (is_vpc) + { + agtv_array = agtv_materialize_vle_edges_slice( + agt_array, lower_index, upper_index); + agt_result = agtype_value_to_agtype(agtv_array); + + PG_FREE_IF_COPY(agt_array, 0); + PG_FREE_IF_COPY(agt_lidx, 1); + PG_FREE_IF_COPY(agt_uidx, 2); + + PG_RETURN_POINTER(agt_result); + } + /* build our result array */ memset(&result, 0, sizeof(agtype_in_state)); @@ -5230,7 +5350,7 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) { agtype *agt_arg, *agt_item; agtype_iterator *it_array, *it_item; - agtype_value *agtv_arg, agtv_item, agtv_elem; + agtype_value agtv_item, agtv_elem; uint32 array_size = 0; bool result = false; bool is_vpc; @@ -5252,11 +5372,9 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) } is_vpc = AGT_ROOT_IS_VPC(agt_arg); - /* If we have vpc as arg, get the agtype_value AGTV_ARRAY of edges */ if (is_vpc) { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - array_size = agtv_arg->val.array.num_elems; + array_size = (uint32)agtv_vle_edge_count(agt_arg); } /* Else we need to iterate agtype_container */ else @@ -5305,12 +5423,47 @@ Datum agtype_in_operator(PG_FUNCTION_ARGS) } } + if (is_vpc && agtv_item.type == AGTV_EDGE) + { + agtype_value *agtv_edge_id = AGTYPE_EDGE_GET_ID(&agtv_item); + + Assert(agtv_edge_id != NULL); + Assert(agtv_edge_id->type == AGTV_INTEGER); + + result = agt_vle_contains_edge_id(agt_arg, + agtv_edge_id->val.int_value); + return boolean_to_agtype(result); + } + /* iterate through the array, but stop if we find it */ for (i = 0; i < array_size && !result; i++) { if (is_vpc) { - agtv_elem = agtv_arg->val.array.elems[i]; + agtype_value *agtv_vle_edge = NULL; + + agtv_vle_edge = agtv_materialize_vle_edge_at(agt_arg, i); + Assert(agtv_vle_edge != NULL); + agtv_elem = *agtv_vle_edge; + + /* if both are containers, compare containers */ + if (!IS_A_AGTYPE_SCALAR(&agtv_item) && + !IS_A_AGTYPE_SCALAR(&agtv_elem)) + { + result = (compare_agtype_containers_orderability( + &agt_item->root, agtv_elem.val.binary.data) == 0); + } + /* if both are scalars and of the same type, compare scalars */ + else if (IS_A_AGTYPE_SCALAR(&agtv_item) && + IS_A_AGTYPE_SCALAR(&agtv_elem) && + agtv_item.type == agtv_elem.type) + { + result = (compare_agtype_scalar_values(&agtv_item, + &agtv_elem) == 0); + } + + pfree_agtype_value(agtv_vle_edge); + continue; } else { @@ -6797,7 +6950,6 @@ PG_FUNCTION_INFO_V1(age_head); Datum age_head(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; agtype_value borrowed_result; agtype *result; @@ -6824,16 +6976,11 @@ Datum age_head(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - - /* if we have an empty list, return a null */ - if (agtv_arg->val.array.num_elems == 0) + agtv_result = agtv_materialize_vle_edge_at(agt_arg, 0); + if (agtv_result == NULL) { PG_RETURN_NULL(); } - - /* get the first element of the array */ - agtv_result = &agtv_arg->val.array.elems[0]; } else { @@ -6868,7 +7015,6 @@ PG_FUNCTION_INFO_V1(age_last); Datum age_last(PG_FUNCTION_ARGS) { agtype *agt_arg = NULL; - agtype_value *agtv_arg = NULL; agtype_value *agtv_result = NULL; agtype_value borrowed_result; agtype *result; @@ -6896,18 +7042,11 @@ Datum age_last(PG_FUNCTION_ARGS) */ if (AGT_ROOT_IS_VPC(agt_arg)) { - agtv_arg = agtv_materialize_vle_edges(agt_arg); - - size = agtv_arg->val.array.num_elems; - - /* if we have an empty list, return a null */ - if (size == 0) + agtv_result = agtv_materialize_vle_edge_at(agt_arg, -1); + if (agtv_result == NULL) { PG_RETURN_NULL(); } - - /* get the first element of the array */ - agtv_result = &agtv_arg->val.array.elems[size-1]; } else { @@ -6953,6 +7092,7 @@ Datum age_tail(PG_FUNCTION_ARGS) agtype_in_state agis_result; int count; int i; + bool is_vpc = false; /* check number of arguments */ if (PG_NARGS() < 1 || PG_NARGS() > 1) @@ -6978,13 +7118,30 @@ Datum age_tail(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + is_vpc = AGT_ROOT_IS_VPC(agt_arg); + /* check for an array */ - if (!AGT_ROOT_IS_ARRAY(agt_arg) || AGT_ROOT_IS_SCALAR(agt_arg)) + if ((!AGT_ROOT_IS_ARRAY(agt_arg) && !is_vpc) || + AGT_ROOT_IS_SCALAR(agt_arg)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("tail() argument must resolve to a list or null"))); } + if (is_vpc) + { + count = agtv_vle_edge_count(agt_arg); + if (count <= 1) + { + PG_RETURN_POINTER(build_empty_agtype_array()); + } + + agt_result = agtype_value_to_agtype( + agtv_materialize_vle_edges_slice(agt_arg, Min(count, 1), count)); + + PG_RETURN_POINTER(agt_result); + } + count = AGT_ROOT_COUNT(agt_arg); /* @@ -7110,6 +7267,15 @@ Datum age_length(PG_FUNCTION_ARGS) PG_RETURN_NULL(); agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtv_result.type = AGTV_INTEGER; + agtv_result.val.int_value = agtv_vle_edge_count(agt_arg); + + PG_RETURN_POINTER(agtype_value_to_agtype(&agtv_result)); + } + /* check for a scalar */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -8006,10 +8172,7 @@ Datum age_size(PG_FUNCTION_ARGS) } else if (AGT_ROOT_IS_VPC(agt_arg)) { - agtype_value *agtv_value; - - agtv_value = agtv_materialize_vle_edges(agt_arg); - result = agtv_value->val.array.num_elems; + result = agtv_vle_edge_count(agt_arg); } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { @@ -8180,9 +8343,7 @@ Datum age_isempty(PG_FUNCTION_ARGS) } else if (AGT_ROOT_IS_VPC(agt_arg)) { - agtype_value *agtv_edges = agtv_materialize_vle_edges(agt_arg); - - result = agtv_edges->val.array.num_elems; + result = agtv_vle_edge_count(agt_arg); } else if (AGT_ROOT_IS_ARRAY(agt_arg)) { @@ -8699,7 +8860,6 @@ Datum age_reverse(PG_FUNCTION_ARGS) agtype_value agtv_value; agtype_value *agtv_result = NULL; bool value_needs_free = false; - agtype_in_state result; agtype_parse_state *parse_state = NULL; agtype_value elem = {0}; agtype_iterator *it = NULL; @@ -8769,24 +8929,14 @@ Datum age_reverse(PG_FUNCTION_ARGS) } else if (AGT_ROOT_IS_VPC(agt_arg)) { - elems = agtv_materialize_vle_edges(agt_arg); - num_elems = elems->val.array.num_elems; - - /* build our result array */ - memset(&result, 0, sizeof(agtype_in_state)); - - result.res = push_agtype_value(&result.parse_state, - WAGT_BEGIN_ARRAY, NULL); - - for (i = num_elems-1; i >= 0; i--) + if (agtv_vle_edge_count(agt_arg) == 0) { - result.res = push_agtype_value(&result.parse_state, WAGT_ELEM, - &elems->val.array.elems[i]); + PG_RETURN_POINTER(build_empty_agtype_array()); } - result.res = push_agtype_value(&result.parse_state, WAGT_END_ARRAY, NULL); + agtv_result = agtv_materialize_vle_edges_reversed(agt_arg); - PG_RETURN_POINTER(agtype_value_to_agtype(result.res)); + PG_RETURN_POINTER(agtype_value_to_agtype(agtv_result)); } else { @@ -12717,6 +12867,16 @@ Datum age_nodes(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtype_value *agtv_nodes = agtv_materialize_vle_nodes(agt_arg); + + result = agtype_value_to_agtype(agtv_nodes); + pfree_agtype_value(agtv_nodes); + PG_RETURN_POINTER(result); + } + /* check for a scalar object */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) { @@ -12860,6 +13020,16 @@ Datum age_relationships(PG_FUNCTION_ARGS) } agt_arg = AG_GET_ARG_AGTYPE_P(0); + if (AGT_ROOT_IS_BINARY(agt_arg) && + AGT_ROOT_BINARY_FLAGS(agt_arg) == AGT_FBINARY_TYPE_VLE_PATH) + { + agtype_value *agtv_edges = agtv_materialize_vle_edges(agt_arg); + + result = agtype_value_to_agtype(agtv_edges); + pfree_agtype_value(agtv_edges); + PG_RETURN_POINTER(result); + } + /* check for a scalar object */ if (!AGT_ROOT_IS_SCALAR(agt_arg)) { diff --git a/src/backend/utils/adt/agtype_util.c b/src/backend/utils/adt/agtype_util.c index 484cef014..752e6ffee 100644 --- a/src/backend/utils/adt/agtype_util.c +++ b/src/backend/utils/adt/agtype_util.c @@ -592,6 +592,119 @@ agtype_value *find_agtype_value_from_container(agtype_container *container, return NULL; } +/* + * No-copy variant of find_agtype_value_from_container(). + * + * Strings, numerics, and nested binary containers point into the original + * container. Extended composite values still allocate during deserialization; + * needs_free tells callers when pfree_agtype_value_content() is required. + */ +bool find_agtype_value_from_container_no_copy(agtype_container *container, + uint32 flags, + agtype_value *key, + agtype_value *result, + bool *needs_free) +{ + agtentry *children = container->children; + int count = AGTYPE_CONTAINER_SIZE(container); + + Assert((flags & ~(AGT_FARRAY | AGT_FOBJECT)) == 0); + Assert(result != NULL); + + if (needs_free != NULL) + { + *needs_free = false; + } + + if (count <= 0) + { + return false; + } + + if ((flags & AGT_FARRAY) && AGTYPE_CONTAINER_IS_ARRAY(container)) + { + char *base_addr = (char *)(children + count); + uint32 offset = 0; + int i; + + for (i = 0; i < count; i++) + { + fill_agtype_value_no_copy(container, i, base_addr, offset, result); + + if (key->type == result->type && + compare_agtype_scalar_values(key, result) == 0) + { + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + return true; + } + + if (result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH) + { + pfree_agtype_value_content(result); + } + AGTE_ADVANCE_OFFSET(offset, children[i]); + } + } + else if ((flags & AGT_FOBJECT) && AGTYPE_CONTAINER_IS_OBJECT(container)) + { + char *base_addr = (char *)(children + count * 2); + uint32 stop_low = 0; + uint32 stop_high = count; + + Assert(key->type == AGTV_STRING); + + while (stop_low < stop_high) + { + uint32 stop_middle; + int difference; + agtype_value candidate; + + stop_middle = stop_low + (stop_high - stop_low) / 2; + + candidate.type = AGTV_STRING; + candidate.val.string.val = + base_addr + get_agtype_offset(container, stop_middle); + candidate.val.string.len = get_agtype_length(container, + stop_middle); + + difference = length_compare_agtype_string_value(&candidate, key); + + if (difference == 0) + { + int index = stop_middle + count; + + fill_agtype_value_no_copy(container, index, base_addr, + get_agtype_offset(container, index), + result); + if (needs_free != NULL) + { + *needs_free = result->type == AGTV_VERTEX || + result->type == AGTV_EDGE || + result->type == AGTV_PATH; + } + return true; + } + else if (difference < 0) + { + stop_low = stop_middle + 1; + } + else + { + stop_high = stop_middle; + } + } + } + + return false; +} + /* * Get i-th value of an agtype array. * diff --git a/src/include/utils/age_global_graph.h b/src/include/utils/age_global_graph.h index 2a1cf3582..f7442df7d 100644 --- a/src/include/utils/age_global_graph.h +++ b/src/include/utils/age_global_graph.h @@ -56,6 +56,12 @@ graphid get_vertex_entry_id(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_in(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_out(vertex_entry *ve); ListGraphId *get_vertex_entry_edges_self(vertex_entry *ve); +ListGraphId *get_vertex_entry_edges_in_for_label(vertex_entry *ve, + Oid edge_label_table_oid); +ListGraphId *get_vertex_entry_edges_out_for_label(vertex_entry *ve, + Oid edge_label_table_oid); +ListGraphId *get_vertex_entry_edges_self_for_label(vertex_entry *ve, + Oid edge_label_table_oid); Oid get_vertex_entry_label_table_oid(vertex_entry *ve); char *get_vertex_entry_label_name(vertex_entry *ve); Datum get_vertex_entry_properties(vertex_entry *ve); diff --git a/src/include/utils/age_vle.h b/src/include/utils/age_vle.h index 27106d294..a6e2f2e70 100644 --- a/src/include/utils/age_vle.h +++ b/src/include/utils/age_vle.h @@ -40,10 +40,34 @@ agtype *agt_materialize_vle_path(agtype *agt_arg_vpc); * agtype_value. */ agtype_value *agtv_materialize_vle_path(agtype *agt_arg_vpc); +/* + * Append the interior edge/vertex values of a VLE path container into an + * existing path builder. Returns the number of appended values. + */ +int64 agt_vle_append_path_interior(agtype *agt_arg_vpc, + agtype_in_state *result); /* * Exposed helper function to make an agtype_value AGTV_ARRAY of edges from a * VLE_path_container. */ agtype_value *agtv_materialize_vle_edges(agtype *agt_arg_vpc); +/* + * Exposed helper function to materialize one edge from a VLE_path_container. + * Returns NULL when the index is out of bounds. + */ +agtype_value *agtv_materialize_vle_edge_at(agtype *agt_arg_vpc, + int64 edge_index); +/* + * Exposed helper function to materialize a normalized half-open edge slice + * from a VLE_path_container. + */ +agtype_value *agtv_materialize_vle_edges_slice(agtype *agt_arg_vpc, + int64 lower_index, + int64 upper_index); +agtype_value *agtv_materialize_vle_edges_reversed(agtype *agt_arg_vpc); +agtype_value *agtv_materialize_vle_nodes(agtype *agt_arg_vpc); +agtype *agt_vle_edge_properties_at(agtype *agt_arg_vpc, int64 edge_index); +bool agt_vle_contains_edge_id(agtype *agt_arg_vpc, graphid edge_id); +int64 agtv_vle_edge_count(agtype *agt_arg_vpc); #endif diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h index 94d1fff87..dcd5b26ee 100644 --- a/src/include/utils/agtype.h +++ b/src/include/utils/agtype.h @@ -567,6 +567,11 @@ int compare_agtype_containers_orderability(agtype_container *a, agtype_value *find_agtype_value_from_container(agtype_container *container, uint32 flags, agtype_value *key); +bool find_agtype_value_from_container_no_copy(agtype_container *container, + uint32 flags, + agtype_value *key, + agtype_value *result, + bool *needs_free); agtype_value *get_ith_agtype_value_from_container(agtype_container *container, uint32 i); bool get_ith_agtype_value_from_container_no_copy(agtype_container *container,