This is an automated email from the ASF dual-hosted git repository.
jgemignani pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/age.git
The following commit(s) were added to refs/heads/master by this push:
new b3a00eea Add index scan (#2351)
b3a00eea is described below
commit b3a00eea32c3faa93c43e0d91c0a75671e48ce65
Author: a_bondar <[email protected]>
AuthorDate: Wed Apr 15 01:23:53 2026 +0700
Add index scan (#2351)
* Add index scan
This commit fixes performance degradation during insertion scenarios by
replacing SeqScan with IndexScan.
Motivation / Problem:
As a result of load testing, a significant performance degradation was
found in insertion scenarios. The scenarios
used were taken from an open-source benchmark and rewritten in pure SQL.
Examples of the queries can be
found here:
https://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-1.cypher
https://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-6.cypher
https://github.com/ldbc/ldbc_snb_interactive_v1_impls/blob/main/cypher/queries/interactive-update-7.cypher
Perf analysis showed that the main bottleneck is the entity_exists
function. The root cause lies in the use of a
Sequential Scan (SeqScan) to check for the existence of an entity prior to
insertion. The time complexity of a
SeqScan is O(N), meaning the search time grows linearly as the number of
rows in the table increases. The
larger the graph became, the longer each individual insertion took. This
led to a drop in TPS regardless of
the concurrency level (the issue was consistently reproduced with both 1
and 30 threads).
Co-authored-by: Daria Barsukova <[email protected]>
Co-authored-by: Alexandra Bondar <[email protected]>
* Address code review commets
* Address code review commets
* Add brackets
---------
Co-authored-by: Alexandra Bondar <[email protected]>
Co-authored-by: Daria Barsukova <[email protected]>
---
src/backend/catalog/ag_label.c | 123 ++++++---
src/backend/executor/cypher_delete.c | 411 ++++++++++++++++++++++---------
src/backend/executor/cypher_set.c | 189 ++++++++++----
src/backend/executor/cypher_utils.c | 47 +++-
src/backend/utils/adt/age_global_graph.c | 124 ++++++++--
src/backend/utils/adt/agtype.c | 116 ++++++++-
src/include/executor/cypher_utils.h | 6 +
src/include/utils/agtype.h | 1 +
8 files changed, 781 insertions(+), 236 deletions(-)
diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c
index 54c31ef3..d407626f 100644
--- a/src/backend/catalog/ag_label.c
+++ b/src/backend/catalog/ag_label.c
@@ -21,6 +21,7 @@
#include "access/genam.h"
#include "catalog/indexing.h"
+#include "catalog/namespace.h"
#include "executor/executor.h"
#include "nodes/makefuncs.h"
#include "utils/builtins.h"
@@ -297,49 +298,115 @@ List *get_all_edge_labels_per_graph(EState *estate, Oid
graph_oid)
HeapTuple tuple;
TupleTableSlot *slot;
ResultRelInfo *resultRelInfo;
-
- /* 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));
+ Oid index_oid;
/* setup the table to be scanned */
- ag_label = table_open(ag_label_relation_id(), RowExclusiveLock);
- scan_desc = table_beginscan(ag_label, estate->es_snapshot, 2, scan_keys);
+ ag_label = table_open(ag_label_relation_id(), AccessShareLock);
+
+ index_oid = find_usable_btree_index_for_attr(ag_label,
Anum_ag_label_graph);
resultRelInfo = create_entity_result_rel_info(estate, "ag_catalog",
"ag_label");
- slot = ExecInitExtraTupleSlot(
- estate, RelationGetDescr(resultRelInfo->ri_RelationDesc),
- &TTSOpsHeapTuple);
-
- /* scan through the results and get all the label names. */
- while(true)
+ if (OidIsValid(index_oid))
+ {
+ 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))
+ {
+ 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);
+ }
+ }
+
+ index_endscan(index_scan_desc);
+ index_close(index_rel, AccessShareLock);
+ }
+ else
{
- Name label;
- bool isNull;
- Datum datum;
+ slot = ExecInitExtraTupleSlot(
+ estate, RelationGetDescr(resultRelInfo->ri_RelationDesc),
+ &TTSOpsHeapTuple);
- tuple = heap_getnext(scan_desc, ForwardScanDirection);
+ /* 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));
- /* no more labels to process */
- if (!HeapTupleIsValid(tuple))
- break;
+ scan_desc = table_beginscan(ag_label, estate->es_snapshot, 2,
scan_keys);
- ExecStoreHeapTuple(tuple, slot, false);
+ /* scan through the results and get all the label names. */
+ while(true)
+ {
+ Name label;
+ Name lval;
+ bool isNull;
+ Datum datum;
- datum = slot_getattr(slot, Anum_ag_label_name, &isNull);
- label = DatumGetName(datum);
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
- labels = lappend(labels, label);
- }
+ /* no more labels to process */
+ if (!HeapTupleIsValid(tuple))
+ break;
+
+ ExecStoreHeapTuple(tuple, slot, false);
+
+ datum = slot_getattr(slot, Anum_ag_label_name, &isNull);
+ label = DatumGetName(datum);
- table_endscan(scan_desc);
+ lval = (Name) palloc(NAMEDATALEN);
+ namestrcpy(lval, NameStr(*label));
+ labels = lappend(labels, lval);
+ }
+
+ table_endscan(scan_desc);
+ }
destroy_entity_result_rel_info(resultRelInfo);
- table_close(resultRelInfo->ri_RelationDesc, RowExclusiveLock);
+ table_close(resultRelInfo->ri_RelationDesc, AccessShareLock);
return labels;
}
diff --git a/src/backend/executor/cypher_delete.c
b/src/backend/executor/cypher_delete.c
index 0b486ad5..58503ec2 100644
--- a/src/backend/executor/cypher_delete.c
+++ b/src/backend/executor/cypher_delete.c
@@ -376,6 +376,8 @@ static void process_delete_list(CustomScanState *node)
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));
@@ -385,18 +387,34 @@ static void process_delete_list(CustomScanState *node)
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);
+
foreach(lc, css->delete_data->delete_items)
{
cypher_delete_item *item;
agtype_value *original_entity_value, *id, *label;
ScanKeyData scan_keys[1];
- TableScanDesc scan_desc;
+ TableScanDesc scan_desc = NULL;
ResultRelInfo *resultRelInfo;
- HeapTuple heap_tuple;
+ HeapTuple heap_tuple = NULL;
char *label_name;
Integer *pos;
int entity_position;
Oid relid;
+ Relation rel;
+ int id_attr_num;
+ Oid index_oid = InvalidOid;
+ TupleTableSlot *slot = NULL;
+ Relation index_rel = NULL;
+ IndexScanDesc index_scan_desc = NULL;
+ bool shouldFree = false;
+ IndexCacheEntry *idx_entry;
+ bool found_idx_entry;
item = lfirst(lc);
@@ -415,7 +433,8 @@ static void process_delete_list(CustomScanState *node)
label_name = pnstrdup(label->val.string.val, label->val.string.len);
resultRelInfo = create_entity_result_rel_info(estate,
css->delete_data->graph_name, label_name);
- relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ rel = resultRelInfo->ri_RelationDesc;
+ relid = RelationGetRelid(rel);
/*
* Setup the scan key to require the id field on-disc to match the
@@ -423,12 +442,14 @@ static void process_delete_list(CustomScanState *node)
*/
if (original_entity_value->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));
}
else if (original_entity_value->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));
@@ -439,82 +460,214 @@ 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);
+
+ if (!found_idx_entry)
+ {
+ idx_entry->index_oid = find_usable_btree_index_for_attr(rel,
id_attr_num);
+ }
+
+ index_oid = idx_entry->index_oid;
+
/*
* Setup the scan description, with the correct snapshot and scan keys.
*/
estate->es_snapshot->curcid = GetCurrentCommandId(false);
estate->es_output_cid = GetCurrentCommandId(false);
- scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc,
- estate->es_snapshot, 1, scan_keys);
- /* Retrieve the tuple. */
- heap_tuple = heap_getnext(scan_desc, ForwardScanDirection);
-
- /*
- * If the heap tuple still exists (It wasn't deleted after this
variable
- * was created) we can delete it. Otherwise, its safe to skip this
- * delete.
- */
- if (!HeapTupleIsValid(heap_tuple))
+ if (OidIsValid(index_oid))
{
- table_endscan(scan_desc);
- destroy_entity_result_rel_info(resultRelInfo);
+ slot = table_slot_create(rel, NULL);
- continue;
+ index_rel = index_open(index_oid, RowExclusiveLock);
+ index_scan_desc = index_beginscan(rel, index_rel,
estate->es_snapshot, NULL, 1, 0);
+ index_rescan(index_scan_desc, scan_keys, 1, NULL, 0);
+
+ if (index_getnext_slot(index_scan_desc, ForwardScanDirection,
slot))
+ {
+ heap_tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+ }
+ }
+ else
+ {
+ scan_desc = table_beginscan(rel, estate->es_snapshot, 1,
scan_keys);
+ /* Retrieve the tuple. */
+ heap_tuple = heap_getnext(scan_desc, ForwardScanDirection);
}
- /* Check RLS security quals (USING policy) before delete */
- if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED)
+ if (HeapTupleIsValid(heap_tuple))
{
- RLSCacheEntry *entry;
- bool found;
+ bool passed_rls = true;
- /* Get cached security quals and slot for this label */
- entry = hash_search(qual_cache, &relid, HASH_ENTER, &found);
- if (!found)
+ /* Check RLS security quals (USING policy) before delete */
+ if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED)
{
- entry->qualExprs = setup_security_quals(resultRelInfo, estate,
- node, CMD_DELETE);
- entry->slot = ExecInitExtraTupleSlot(
- estate, RelationGetDescr(resultRelInfo->ri_RelationDesc),
- &TTSOpsHeapTuple);
- entry->withCheckOptions = NIL;
- entry->withCheckOptionExprs = NIL;
+ 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);
+
+ if (!check_security_quals(entry->qualExprs, entry->slot,
econtext))
+ {
+ passed_rls = false;
+ }
}
- ExecStoreHeapTuple(heap_tuple, entry->slot, false);
+ 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)
+ {
+ bool found;
+ hash_search(css->vertex_id_htab, (void
*)&(id->val.int_value),
+ HASH_ENTER, &found);
+ }
- /* Silently skip if USING policy filters out this row */
- if (!check_security_quals(entry->qualExprs, entry->slot, econtext))
+ /* At this point, we are ready to delete the node/vertex. */
+ delete_entity(estate, resultRelInfo, heap_tuple);
+ }
+
+ if (shouldFree)
{
- table_endscan(scan_desc);
- destroy_entity_result_rel_info(resultRelInfo);
- continue;
+ heap_freetuple(heap_tuple);
}
}
- /*
- * 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)
+ if (OidIsValid(index_oid))
{
- bool found;
- hash_search(css->vertex_id_htab, (void *)&(id->val.int_value),
- HASH_ENTER, &found);
+ ExecDropSingleTupleTableSlot(slot);
+ index_endscan(index_scan_desc);
+ index_close(index_rel, RowExclusiveLock);
+ }
+ else
+ {
+ table_endscan(scan_desc);
}
- /* At this point, we are ready to delete the node/vertex. */
- delete_entity(estate, resultRelInfo, heap_tuple);
-
- /* Close the scan and the relation. */
- table_endscan(scan_desc);
destroy_entity_result_rel_info(resultRelInfo);
}
/* Clean up the cache */
hash_destroy(qual_cache);
+ hash_destroy(index_cache);
+}
+
+/*
+ * Helper function to scan an edge table using a specific index (start_id or
end_id)
+ * and delete the connected edges if the vertex is being deleted.
+ */
+static void process_edges_by_index(Oid index_oid,
+ Relation rel,
+ EState *estate,
+ cypher_delete_custom_scan_state *css,
+ ResultRelInfo *resultRelInfo,
+ Oid relid,
+ char *label_name,
+ bool rls_enabled,
+ List *qualExprs,
+ ExprContext *econtext,
+ bool is_pass_two)
+{
+ HASH_SEQ_STATUS hash_status;
+ graphid *vid;
+ Relation index_rel;
+ IndexScanDesc scan;
+ ScanKeyData key;
+ TupleTableSlot *slot;
+
+ slot = table_slot_create(rel, NULL);
+
+ index_rel = index_open(index_oid, RowExclusiveLock);
+ scan = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0);
+
+ /* Initialize ScanKey with a dummy argument (0), updated in the loop */
+ ScanKeyInit(&key, 1, BTEqualStrategyNumber, F_GRAPHIDEQ, 0);
+ hash_seq_init(&hash_status, css->vertex_id_htab);
+
+ while ((vid = (graphid *) hash_seq_search(&hash_status)) != NULL)
+ {
+ /* Update search key with the current ID of the vertex being deleted */
+ key.sk_argument = GRAPHID_GET_DATUM(*vid);
+ index_rescan(scan, &key, 1, NULL, 0);
+
+ while (index_getnext_slot(scan, ForwardScanDirection, slot))
+ {
+ if (is_pass_two)
+ {
+ bool is_null;
+ graphid startid;
+ bool found_startid = false;
+
+ startid = GRAPHID_GET_DATUM(slot_getattr(slot,
Anum_ag_label_edge_table_start_id, &is_null));
+
+ hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND,
&found_startid);
+
+ if (found_startid)
+ {
+ ExecClearTuple(slot);
+ continue;
+ }
+ }
+
+ /* 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)
+ {
+ aclcheck_error(aclresult, OBJECT_TABLE, label_name);
+ }
+
+ /* Check RLS security quals (USING policy) before delete */
+ if (rls_enabled)
+ {
+ if (!check_security_quals(qualExprs, slot, econtext))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("cannot delete edge due to
row-level security policy on \"%s\"",
+ label_name),
+ errhint("DETACH DELETE requires permission
to delete all connected edges.")));
+ }
+ }
+
+ tuple = ExecFetchSlotHeapTuple(slot, true, &shouldFree);
+ delete_entity(estate, resultRelInfo, tuple);
+
+ if (shouldFree)
+ {
+ heap_freetuple(tuple);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg("Cannot delete a vertex that has edge(s). "
+ "Delete the edge(s) first, or try DETACH
DELETE.")));
+ }
+ ExecClearTuple(slot);
+ }
+ }
+ index_endscan(scan);
+ index_close(index_rel, RowExclusiveLock);
+ ExecDropSingleTupleTableSlot(slot);
}
/*
@@ -542,17 +695,16 @@ static void check_for_connected_edges(CustomScanState
*node)
bool rls_enabled = 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);
- relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ rel = resultRelInfo->ri_RelationDesc;
+ relid = RelationGetRelid(rel);
estate->es_snapshot->curcid = GetCurrentCommandId(false);
estate->es_output_cid = GetCurrentCommandId(false);
- scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc,
- estate->es_snapshot, 0, NULL);
- slot = ExecInitExtraTupleSlot(
- estate, RelationGetDescr(resultRelInfo->ri_RelationDesc),
- &TTSOpsHeapTuple);
/*
* For DETACH DELETE with RLS enabled, compile the security qual
@@ -570,86 +722,109 @@ static void check_for_connected_edges(CustomScanState
*node)
}
}
- /* for each row */
- while (true)
- {
- graphid startid;
- graphid endid;
- bool isNull;
- bool found_startid = false;
- bool found_endid = 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);
- tuple = heap_getnext(scan_desc, ForwardScanDirection);
+ 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_enabled, qualExprs,
econtext, false);
+
+ /* 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);
+ }
+ else
+ {
+ scan_desc = table_beginscan(rel, estate->es_snapshot, 0, NULL);
+ slot = ExecInitExtraTupleSlot(
+ estate, RelationGetDescr(rel),
+ &TTSOpsHeapTuple);
- /* no more tuples to process, break and scan the next label. */
- if (!HeapTupleIsValid(tuple))
+ /* for each row */
+ while (true)
{
- break;
- }
+ graphid startid;
+ graphid endid;
+ bool isNull;
+ bool found_startid = false;
+ bool found_endid = false;
- ExecStoreHeapTuple(tuple, slot, false);
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
- 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));
+ /* no more tuples to process, break and scan the next label. */
+ if (!HeapTupleIsValid(tuple))
+ {
+ break;
+ }
- hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND,
- &found_startid);
+ ExecStoreHeapTuple(tuple, slot, false);
- if (!found_startid)
- {
- hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND,
- &found_endid);
- }
+ 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));
- if (found_startid || found_endid)
- {
- if (css->delete_data->detach)
+ hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND,
+ &found_startid);
+
+ if (!found_startid)
{
- AclResult aclresult;
+ hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND,
+ &found_endid);
+ }
- /* Check that the user has DELETE permission on the edge
table */
- aclresult = pg_class_aclcheck(relid, GetUserId(),
ACL_DELETE);
- if (aclresult != ACLCHECK_OK)
+ if (found_startid || found_endid)
+ {
+ if (css->delete_data->detach)
{
- aclcheck_error(aclresult, OBJECT_TABLE, label_name);
- }
+ AclResult aclresult;
- /* Check RLS security quals (USING policy) before delete */
- if (rls_enabled)
- {
- /*
- * For DETACH DELETE, error out if edge RLS check
fails.
- * Unlike normal DELETE which silently skips, we cannot
- * silently skip edges here as it would leave dangling
- * edges pointing to deleted vertices.
- */
- if (!check_security_quals(qualExprs, slot, econtext))
+ /* Check that the user has DELETE permission on the
edge table */
+ aclresult = pg_class_aclcheck(relid, GetUserId(),
ACL_DELETE);
+ if (aclresult != ACLCHECK_OK)
{
- ereport(ERROR,
- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
- errmsg("cannot delete edge due to
row-level security policy on \"%s\"",
- label_name),
- errhint("DETACH DELETE requires
permission to delete all connected edges.")));
+ aclcheck_error(aclresult, OBJECT_TABLE,
label_name);
}
- }
- delete_entity(estate, resultRelInfo, tuple);
- }
- else
- {
- ereport(
- ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg(
- "Cannot delete a vertex that has edge(s). "
- "Delete the edge(s) first, or try DETACH
DELETE.")));
+ /* Check RLS security quals (USING policy) before
delete */
+ if (rls_enabled)
+ {
+ /*
+ * For DETACH DELETE, error out if edge RLS check
fails.
+ * Unlike normal DELETE which silently skips, we
cannot
+ * silently skip edges here as it would leave
dangling
+ * edges pointing to deleted vertices.
+ */
+ if (!check_security_quals(qualExprs, slot,
econtext))
+ {
+ ereport(ERROR,
+
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("cannot delete edge due to
row-level security policy on \"%s\"",
+ label_name),
+ errhint("DETACH DELETE requires
permission to delete all connected edges.")));
+ }
+ }
+
+ delete_entity(estate, resultRelInfo, tuple);
+ }
+ else
+ {
+ ereport(
+ ERROR,
+ (errcode(ERRCODE_INTERNAL_ERROR),
+ errmsg(
+ "Cannot delete a vertex that has edge(s). "
+ "Delete the edge(s) first, or try DETACH
DELETE.")));
+ }
}
}
+
+ table_endscan(scan_desc);
}
- table_endscan(scan_desc);
destroy_entity_result_rel_info(resultRelInfo);
}
}
diff --git a/src/backend/executor/cypher_set.c
b/src/backend/executor/cypher_set.c
index a1063af3..09d3d3b5 100644
--- a/src/backend/executor/cypher_set.c
+++ b/src/backend/executor/cypher_set.c
@@ -383,6 +383,8 @@ static void process_update_list(CustomScanState *node)
int lidx = 0;
HTAB *qual_cache = NULL;
HASHCTL hashctl;
+ HTAB *index_cache = NULL;
+ HASHCTL idx_hashctl;
/* allocate an array to hold the last update index of each 'entity' */
luindex = palloc0(sizeof(int) * scanTupleSlot->tts_nvalid);
@@ -395,6 +397,13 @@ static void process_update_list(CustomScanState *node)
qual_cache = hash_create("update_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("update_index_cache", 8, &idx_hashctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+
/*
* 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
@@ -439,6 +448,11 @@ static void process_update_list(CustomScanState *node)
HeapTuple heap_tuple;
char *clause_name = css->set_list->clause_name;
int cid;
+ Oid index_oid = InvalidOid;
+ Relation rel;
+ Oid relid;
+ IndexCacheEntry *idx_entry;
+ bool found_idx_entry;
update_item = (cypher_update_item *)lfirst(lc);
@@ -538,6 +552,17 @@ static void process_update_list(CustomScanState *node)
resultRelInfo = create_entity_result_rel_info(
estate, css->set_list->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);
@@ -546,7 +571,6 @@ static void process_update_list(CustomScanState *node)
if (check_enable_rls(resultRelInfo->ri_RelationDesc->rd_id,
InvalidOid, true) == RLS_ENABLED)
{
- Oid relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
RLSCacheEntry *entry;
bool found;
@@ -628,60 +652,132 @@ static void process_update_list(CustomScanState *node)
if (luindex[update_item->entity_position - 1] == lidx)
{
- /*
- * Setup the scan key to require the id field on-disc to match the
- * entity's graphid.
- */
- ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_GRAPHIDEQ,
- GRAPHID_GET_DATUM(id->val.int_value));
- /*
- * Setup the scan description, with the correct snapshot and scan
- * keys.
- */
- scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc,
- estate->es_snapshot, 1, scan_keys);
- /* Retrieve the tuple. */
- heap_tuple = heap_getnext(scan_desc, ForwardScanDirection);
-
- /*
- * If the heap tuple still exists (It wasn't deleted between the
- * match and this SET/REMOVE) update the heap_tuple.
- */
- if (HeapTupleIsValid(heap_tuple))
- {
- bool should_update = true;
- Oid relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
-
- /* Check RLS security quals (USING policy) before update */
- if (check_enable_rls(relid, InvalidOid, true) == RLS_ENABLED)
+ if (OidIsValid(index_oid))
+ {
+ Relation index_rel;
+ IndexScanDesc idx_scan_desc;
+ TupleTableSlot *index_slot;
+
+ index_rel = index_open(index_oid, RowExclusiveLock);
+
+ /*
+ * Setup the scan key to require the id field on-disc to match
the
+ * entity's graphid.
+ */
+ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber,
F_GRAPHIDEQ,
+ GRAPHID_GET_DATUM(id->val.int_value));
+
+ index_slot = table_slot_create(rel, NULL);
+ idx_scan_desc = index_beginscan(rel, index_rel,
estate->es_snapshot, NULL, 1, 0);
+ index_rescan(idx_scan_desc, scan_keys, 1, NULL, 0);
+
+ if (index_getnext_slot(idx_scan_desc, ForwardScanDirection,
index_slot))
{
- RLSCacheEntry *entry;
+ bool shouldFree;
+
+ /* Retrieve the tuple from the slot */
+ heap_tuple = ExecFetchSlotHeapTuple(index_slot, true,
&shouldFree);
- /* Entry was already created earlier when setting up WCOs
*/
- entry = hash_search(qual_cache, &relid, HASH_FIND, NULL);
- if (!entry)
+ if (HeapTupleIsValid(heap_tuple))
{
- ereport(ERROR,
- (errcode(ERRCODE_INTERNAL_ERROR),
- errmsg("missing RLS cache entry for relation
%u",
- relid)));
+ bool should_update = true;
+ HeapTuple original_tuple = heap_tuple;
+
+ /* Check RLS security quals (USING policy) before
update */
+ if (check_enable_rls(relid, InvalidOid, true) ==
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,
+ econtext);
+ }
+
+ /* Silently skip if USING policy filters out this row
*/
+ if (should_update)
+ {
+ heap_tuple = update_entity_tuple(resultRelInfo,
slot, estate,
+ original_tuple);
+ }
+
+ if (shouldFree)
+ {
+ heap_freetuple(original_tuple);
+ }
}
-
- ExecStoreHeapTuple(heap_tuple, entry->slot, false);
- should_update = check_security_quals(entry->qualExprs,
- entry->slot,
- econtext);
}
- /* Silently skip if USING policy filters out this row */
- if (should_update)
+ ExecDropSingleTupleTableSlot(index_slot);
+ index_endscan(idx_scan_desc);
+ index_close(index_rel, RowExclusiveLock);
+ }
+ else
+ {
+ /*
+ * Setup the scan key to require the id field on-disc to match
the
+ * entity's graphid.
+ */
+ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber,
F_GRAPHIDEQ,
+ GRAPHID_GET_DATUM(id->val.int_value));
+ /*
+ * Setup the scan description, with the correct snapshot and
scan
+ * keys.
+ */
+ scan_desc = table_beginscan(resultRelInfo->ri_RelationDesc,
+ estate->es_snapshot, 1, scan_keys);
+ /* Retrieve the tuple. */
+ heap_tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ /*
+ * If the heap tuple still exists (It wasn't deleted between the
+ * match and this SET/REMOVE) update the heap_tuple.
+ */
+ if (HeapTupleIsValid(heap_tuple))
{
- heap_tuple = update_entity_tuple(resultRelInfo, slot,
estate,
- heap_tuple);
+ bool should_update = true;
+
+ /* Check RLS security quals (USING policy) before update */
+ if (check_enable_rls(relid, InvalidOid, true) ==
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,
+ econtext);
+ }
+
+ /* Silently skip if USING policy filters out this row */
+ if (should_update)
+ {
+ heap_tuple = update_entity_tuple(resultRelInfo, slot,
estate,
+ heap_tuple);
+ }
}
+ /* close the ScanDescription */
+ table_endscan(scan_desc);
}
- /* close the ScanDescription */
- table_endscan(scan_desc);
}
estate->es_snapshot->curcid = cid;
@@ -695,6 +791,7 @@ static void process_update_list(CustomScanState *node)
/* Clean up the cache */
hash_destroy(qual_cache);
+ hash_destroy(index_cache);
/* free our lookup array */
pfree_if_not_null(luindex);
diff --git a/src/backend/executor/cypher_utils.c
b/src/backend/executor/cypher_utils.c
index 94028423..8237cdcc 100644
--- a/src/backend/executor/cypher_utils.c
+++ b/src/backend/executor/cypher_utils.c
@@ -208,6 +208,8 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid
id)
HeapTuple tuple;
Relation rel;
bool result = true;
+ TupleTableSlot *slot;
+ Oid index_oid = InvalidOid;
CommandId saved_curcid;
/*
@@ -238,20 +240,47 @@ bool entity_exists(EState *estate, Oid graph_oid, graphid
id)
GetCurrentCommandId(false));
rel = table_open(label->relation, RowExclusiveLock);
- scan_desc = table_beginscan(rel, estate->es_snapshot, 1, scan_keys);
- tuple = heap_getnext(scan_desc, ForwardScanDirection);
+ index_oid = find_usable_btree_index_for_attr(rel, 1);
- /*
- * If a single tuple was returned, the tuple is still valid, otherwise'
- * set to false.
- */
- if (!HeapTupleIsValid(tuple))
+ if (OidIsValid(index_oid))
{
- result = false;
+ IndexScanDesc index_scan_desc;
+ Relation index_rel;
+
+ slot = table_slot_create(rel, NULL);
+
+ index_rel = index_open(index_oid, AccessShareLock);
+
+ index_scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot,
NULL, 1, 0);
+ index_rescan(index_scan_desc, scan_keys, 1, NULL, 0);
+
+ if (!index_getnext_slot(index_scan_desc, ForwardScanDirection, slot))
+ {
+ result = false;
+ }
+
+ index_endscan(index_scan_desc);
+ index_close(index_rel, AccessShareLock);
+ ExecDropSingleTupleTableSlot(slot);
+ }
+ else
+ {
+ scan_desc = table_beginscan(rel, estate->es_snapshot, 1, scan_keys);
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ /*
+ * If a single tuple was returned, the tuple is still valid, otherwise'
+ * set to false.
+ */
+ if (!HeapTupleIsValid(tuple))
+ {
+ result = false;
+ }
+
+ table_endscan(scan_desc);
}
- table_endscan(scan_desc);
table_close(rel, RowExclusiveLock);
/* Restore the original curcid */
diff --git a/src/backend/utils/adt/age_global_graph.c
b/src/backend/utils/adt/age_global_graph.c
index 4e5b5863..a9308970 100644
--- a/src/backend/utils/adt/age_global_graph.c
+++ b/src/backend/utils/adt/age_global_graph.c
@@ -204,43 +204,123 @@ static List *get_ag_labels_names(Snapshot snapshot, Oid
graph_oid,
TableScanDesc scan_desc;
HeapTuple tuple;
TupleDesc tupdesc;
+ Oid index_oid = InvalidOid;
/* we need a valid snapshot */
Assert(snapshot != NULL);
- /* 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));
-
/* setup the table to be scanned, ag_label in this case */
ag_label = table_open(ag_label_relation_id(), AccessShareLock);
- scan_desc = table_beginscan(ag_label, snapshot, 2, scan_keys);
/* get the tupdesc - we don't need to release this one */
tupdesc = RelationGetDescr(ag_label);
/* bail if the number of columns differs - this table has 5 */
Assert(tupdesc->natts == Natts_ag_label);
- /* get all of the label names */
- while((tuple = heap_getnext(scan_desc, ForwardScanDirection)) != NULL)
+ /*
+ * 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))
{
- Name label;
- bool is_null = false;
-
- /* 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));
- Assert(!is_null);
- /* add it to our list */
- labels = lappend(labels, label);
+ 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);
+
+ /*
+ * 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));
+
+ idx_scan_desc = index_beginscan(ag_label, index_rel, snapshot, NULL,
1, 0);
+ index_rescan(idx_scan_desc, &key, 1, NULL, 0);
+
+ while (index_getnext_slot(idx_scan_desc, ForwardScanDirection, slot))
+ {
+ 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);
+ }
+
+ 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)
+ {
+ Name label;
+ Name lval;
+ bool is_null = false;
+
+ /* 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));
+
+ Assert(!is_null);
+ /* add it to our list */
+ lval = (Name) palloc(NAMEDATALEN);
+ namestrcpy(lval, NameStr(*label));
+ labels = lappend(labels, lval);
+ }
+
+ /* close up scan */
+ table_endscan(scan_desc);
}
- /* close up scan */
- table_endscan(scan_desc);
table_close(ag_label, AccessShareLock);
return labels;
diff --git a/src/backend/utils/adt/agtype.c b/src/backend/utils/adt/agtype.c
index c0c54e5a..38621955 100644
--- a/src/backend/utils/adt/agtype.c
+++ b/src/backend/utils/adt/agtype.c
@@ -38,6 +38,7 @@
#include "access/genam.h"
#include "access/heapam.h"
#include "catalog/namespace.h"
+#include "catalog/pg_am_d.h"
#include "catalog/pg_collation_d.h"
#include "catalog/pg_operator_d.h"
#include "funcapi.h"
@@ -5624,11 +5625,14 @@ static Datum get_vertex(const char *graph, const char
*vertex_label,
{
ScanKeyData scan_keys[1];
Relation graph_vertex_label;
- TableScanDesc scan_desc;
- HeapTuple tuple;
+ TableScanDesc scan_desc = NULL;
+ HeapTuple tuple = NULL;
TupleDesc tupdesc;
Datum id, properties, result;
AclResult aclresult;
+ TupleTableSlot *slot;
+ Oid index_oid;
+ bool should_free_tuple = false;
/* get the specific graph namespace (schema) */
Oid graph_namespace_oid = get_namespace_oid(graph, false);
@@ -5646,19 +5650,54 @@ static Datum get_vertex(const char *graph, const char
*vertex_label,
aclcheck_error(aclresult, OBJECT_TABLE, vertex_label);
}
- /* initialize the scan key */
- ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_OIDEQ,
- Int64GetDatum(graphid));
-
- /* open the relation (table), begin the scan, and get the tuple */
+ /* open the relation (table) */
graph_vertex_label = table_open(vertex_label_table_oid, ShareLock);
- scan_desc = table_beginscan(graph_vertex_label, snapshot, 1, scan_keys);
- tuple = heap_getnext(scan_desc, ForwardScanDirection);
+
+ index_oid = find_usable_btree_index_for_attr(graph_vertex_label, 1);
+
+ if (OidIsValid(index_oid))
+ {
+ IndexScanDesc index_scan_desc;
+ Relation index_rel;
+
+ index_rel = index_open(index_oid, ShareLock);
+ slot = table_slot_create(graph_vertex_label, NULL);
+
+ /* initialize the scan key using GRAPHIDEQ for index */
+ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber,
+ F_GRAPHIDEQ, Int64GetDatum(graphid));
+
+ index_scan_desc = index_beginscan(graph_vertex_label, index_rel,
+ snapshot, NULL, 1, 0);
+ index_rescan(index_scan_desc, scan_keys, 1, NULL, 0);
+
+ if (index_getnext_slot(index_scan_desc, ForwardScanDirection, slot))
+ {
+ tuple = ExecCopySlotHeapTuple(slot);
+ should_free_tuple = true;
+ }
+
+ index_endscan(index_scan_desc);
+ index_close(index_rel, ShareLock);
+ ExecDropSingleTupleTableSlot(slot);
+ }
+ else
+ {
+ /* fallback to sequential scan */
+ ScanKeyInit(&scan_keys[0], 1, BTEqualStrategyNumber, F_GRAPHIDEQ,
+ GRAPHID_GET_DATUM(graphid));
+
+ scan_desc = table_beginscan(graph_vertex_label, snapshot, 1,
scan_keys);
+ tuple = heap_getnext(scan_desc, ForwardScanDirection);
+ }
/* bail if the tuple isn't valid */
if (!HeapTupleIsValid(tuple))
{
- table_endscan(scan_desc);
+ if (scan_desc)
+ {
+ table_endscan(scan_desc);
+ }
table_close(graph_vertex_label, ShareLock);
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_TABLE),
@@ -5668,7 +5707,15 @@ static Datum get_vertex(const char *graph, const char
*vertex_label,
/* Check RLS policies - error if filtered out */
if (!check_rls_for_tuple(graph_vertex_label, tuple, CMD_SELECT))
{
- table_endscan(scan_desc);
+ if (scan_desc)
+ {
+ table_endscan(scan_desc);
+ }
+ if (should_free_tuple && tuple != NULL)
+ {
+ heap_freetuple(tuple);
+ }
+
table_close(graph_vertex_label, ShareLock);
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -5693,8 +5740,17 @@ static Datum get_vertex(const char *graph, const char
*vertex_label,
/* reconstruct the vertex */
result = DirectFunctionCall3(_agtype_build_vertex, id,
CStringGetDatum(vertex_label), properties);
- /* end the scan and close the relation */
- table_endscan(scan_desc);
+
+ /* end the scan and close the relation with new cleanup logic */
+ if (scan_desc)
+ {
+ table_endscan(scan_desc);
+ }
+ if (should_free_tuple && tuple != NULL)
+ {
+ heap_freetuple(tuple);
+ }
+
table_close(graph_vertex_label, ShareLock);
/* return the vertex datum */
return result;
@@ -12020,6 +12076,40 @@ static int64 get_int64_from_int_datums(Datum d, Oid
type, char *funcname,
return result;
}
+/*
+ * 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)
+{
+List *index_list = RelationGetIndexList(rel);
+ ListCell *ilc;
+ Oid index_oid = InvalidOid;
+
+ 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->indkey.values[0] == attnum &&
+ curr_idx_rel->rd_rel->relam == BTREE_AM_OID &&
+ RelationGetIndexPredicate(curr_idx_rel) == NIL)
+ {
+ index_oid = curr_idx_oid;
+ index_close(curr_idx_rel, AccessShareLock);
+ break;
+ }
+
+ index_close(curr_idx_rel, AccessShareLock);
+ }
+
+ list_free(index_list);
+
+ return index_oid;
+}
+
PG_FUNCTION_INFO_V1(age_range);
/*
* Execution function to implement openCypher range() function
diff --git a/src/include/executor/cypher_utils.h
b/src/include/executor/cypher_utils.h
index 278094e0..cdd8fa33 100644
--- a/src/include/executor/cypher_utils.h
+++ b/src/include/executor/cypher_utils.h
@@ -152,4 +152,10 @@ typedef struct RLSCacheEntry
List *withCheckOptionExprs;
} RLSCacheEntry;
+/* Hash table entry for caching index OIDs per label */
+typedef struct IndexCacheEntry {
+ Oid relid; /* hash key */
+ Oid index_oid;
+} IndexCacheEntry;
+
#endif
diff --git a/src/include/utils/agtype.h b/src/include/utils/agtype.h
index ec912507..807d3795 100644
--- a/src/include/utils/agtype.h
+++ b/src/include/utils/agtype.h
@@ -593,6 +593,7 @@ bool is_numeric_result(agtype_value *lhs, agtype_value
*rhs);
void copy_agtype_value(agtype_parse_state* pstate,
agtype_value* original_agtype_value,
agtype_value **copied_agtype_value, bool is_top_level);
+Oid find_usable_btree_index_for_attr(Relation rel, AttrNumber attnum);
/* agtype.c support functions */
/*