diff --git a/src/backend/catalog/ag_label.c b/src/backend/catalog/ag_label.c index 54c31ef36..ee209418e 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,46 +298,104 @@ List *get_all_edge_labels_per_graph(EState *estate, Oid graph_oid) HeapTuple tuple; TupleTableSlot *slot; ResultRelInfo *resultRelInfo; + Oid index_oid; - /* 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)); + index_oid = get_relname_relid("ag_label_graph_oid_index", + get_namespace_oid("ag_catalog", false)); /* 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); 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, RowExclusiveLock); + + ScanKeyInit(&scan_keys[0], Anum_ag_label_name, 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, RowExclusiveLock); + } 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); - table_endscan(scan_desc); + datum = slot_getattr(slot, Anum_ag_label_name, &isNull); + label = DatumGetName(datum); + + 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); diff --git a/src/backend/executor/cypher_delete.c b/src/backend/executor/cypher_delete.c index 0b486ad5e..a47a63eb8 100644 --- a/src/backend/executor/cypher_delete.c +++ b/src/backend/executor/cypher_delete.c @@ -390,13 +390,20 @@ static void process_delete_list(CustomScanState *node) 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; item = lfirst(lc); @@ -415,7 +422,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 +431,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,77 +449,113 @@ static void process_delete_list(CustomScanState *node) errmsg("DELETE clause can only delete vertices and edges"))); } + { + List *index_list = RelationGetIndexList(rel); + ListCell *ilc; + + 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] == id_attr_num) + { + index_oid = curr_idx_oid; + index_close(curr_idx_rel, AccessShareLock); + break; + } + + index_close(curr_idx_rel, AccessShareLock); + } + list_free(index_list); + } + /* * 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); + ExecStoreHeapTuple(heap_tuple, entry->slot, false); - /* Silently skip if USING policy filters out this row */ - if (!check_security_quals(entry->qualExprs, entry->slot, econtext)) + if (!check_security_quals(entry->qualExprs, entry->slot, econtext)) + { + passed_rls = false; + } + } + + if (passed_rls) { - table_endscan(scan_desc); - destroy_entity_result_rel_info(resultRelInfo); - continue; + /* + * 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); + } + + /* At this point, we are ready to delete the node/vertex. */ + delete_entity(estate, resultRelInfo, heap_tuple); } + + if (shouldFree) + 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); } @@ -542,17 +588,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 +615,260 @@ 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; - - tuple = heap_getnext(scan_desc, ForwardScanDirection); + List *index_list = RelationGetIndexList(rel); + ListCell *ilc; - /* no more tuples to process, break and scan the next label. */ - if (!HeapTupleIsValid(tuple)) + /* Look for indexes on start_id and end_id columns. */ + foreach(ilc, index_list) { - break; + Oid curr_idx_oid = lfirst_oid(ilc); + Relation curr_idx_rel = index_open(curr_idx_oid, AccessShareLock); + + int first_key_attnum = curr_idx_rel->rd_index->indkey.values[0]; + + if (curr_idx_rel->rd_index->indisvalid && + curr_idx_rel->rd_index->indnatts >= 1) + { + /* Check if the index is built on start_id or end_id */ + if (first_key_attnum == Anum_ag_label_edge_table_start_id) + { + start_index_oid = curr_idx_oid; + } + else if (first_key_attnum == Anum_ag_label_edge_table_end_id) + { + end_index_oid = curr_idx_oid; + } + } + index_close(curr_idx_rel, AccessShareLock); } + list_free(index_list); + } - ExecStoreHeapTuple(tuple, slot, false); + if (OidIsValid(start_index_oid) && OidIsValid(end_index_oid)) + { + HASH_SEQ_STATUS hash_status; + graphid *vid; + Relation index_rel; + IndexScanDesc scan; + ScanKeyData key; - 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)); + slot = table_slot_create(rel, NULL); - hash_search(css->vertex_id_htab, (void *)&startid, HASH_FIND, - &found_startid); + /* PASS 1: Find edges where the deleted vertex is the START_ID. + * We only lock the start_index here. + */ + index_rel = index_open(start_index_oid, RowExclusiveLock); + scan = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); - if (!found_startid) - { - hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND, - &found_endid); - } + /* 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); - if (found_startid || found_endid) + while ((vid = (graphid *) hash_seq_search(&hash_status)) != NULL) { - if (css->delete_data->detach) + /* 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)) { - AclResult aclresult; + /* If edge found - delete it (or error if not DETACH) */ + if (css->delete_data->detach) + { + AclResult aclresult; + bool shouldFree; - /* Check that the user has DELETE permission on the edge table */ - aclresult = pg_class_aclcheck(relid, GetUserId(), ACL_DELETE); - if (aclresult != ACLCHECK_OK) + /* 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 { - aclcheck_error(aclresult, OBJECT_TABLE, label_name); + 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); + + /* PASS 2: Find edges where the deleted vertex is the END_ID. + * Now we only lock the end_index. + */ + index_rel = index_open(end_index_oid, RowExclusiveLock); + scan = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); + ScanKeyInit(&key, 1, BTEqualStrategyNumber, F_GRAPHIDEQ, 0); + + /* Reset hash iterator to go through deleted vertices again */ + hash_seq_init(&hash_status, css->vertex_id_htab); + while ((vid = (graphid *) hash_seq_search(&hash_status)) != NULL) + { + key.sk_argument = GRAPHID_GET_DATUM(*vid); + index_rescan(scan, &key, 1, NULL, 0); - /* Check RLS security quals (USING policy) before delete */ - if (rls_enabled) + while (index_getnext_slot(scan, ForwardScanDirection, slot)) + { + if (css->delete_data->detach) { - /* - * 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)) + AclResult aclresult; + bool shouldFree; + + /* 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) { - 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."))); + 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); + } + else + { + scan_desc = table_beginscan(rel, estate->es_snapshot, 0, NULL); + slot = ExecInitExtraTupleSlot( + estate, RelationGetDescr(rel), + &TTSOpsHeapTuple); + + /* for each row */ + while (true) + { + graphid startid; + graphid endid; + bool isNull; + bool found_startid = false; + bool found_endid = false; - delete_entity(estate, resultRelInfo, tuple); + tuple = heap_getnext(scan_desc, ForwardScanDirection); + + /* no more tuples to process, break and scan the next label. */ + if (!HeapTupleIsValid(tuple)) + { + break; } - else + + ExecStoreHeapTuple(tuple, slot, false); + + 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) + { + hash_search(css->vertex_id_htab, (void *)&endid, HASH_FIND, + &found_endid); + } + + if (found_startid || found_endid) { - ereport( - ERROR, - (errcode(ERRCODE_INTERNAL_ERROR), - errmsg( - "Cannot delete a vertex that has edge(s). " - "Delete the edge(s) first, or try DETACH DELETE."))); + 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) + { + aclcheck_error(aclresult, OBJECT_TABLE, label_name); + } + + /* 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 a1063af32..f181ade42 100644 --- a/src/backend/executor/cypher_set.c +++ b/src/backend/executor/cypher_set.c @@ -439,6 +439,8 @@ 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; update_item = (cypher_update_item *)lfirst(lc); @@ -538,6 +540,32 @@ 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; + + /* Check if there is a valid index on the 'id' column */ + { + List *index_list = RelationGetIndexList(rel); + ListCell *ilc; + + foreach(ilc, index_list) + { + Oid curr_idx_oid = lfirst_oid(ilc); + Relation curr_idx_rel = index_open(curr_idx_oid, AccessShareLock); + + /* Check: valid, B-Tree, and the first key column is attribute 1 (id) */ + if (curr_idx_rel->rd_index->indisvalid && + curr_idx_rel->rd_index->indnatts >= 1 && + curr_idx_rel->rd_index->indkey.values[0] == 1) + { + index_oid = curr_idx_oid; + index_close(curr_idx_rel, AccessShareLock); + break; + } + index_close(curr_idx_rel, AccessShareLock); + } + list_free(index_list); + } + slot = ExecInitExtraTupleSlot( estate, RelationGetDescr(resultRelInfo->ri_RelationDesc), &TTSOpsHeapTuple); @@ -628,60 +656,100 @@ 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 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); + scan_desc = index_beginscan(rel, index_rel, estate->es_snapshot, NULL, 1, 0); + index_rescan(scan_desc, scan_keys, 1, NULL, 0); + + if (index_getnext_slot(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))); + heap_tuple = update_entity_tuple(resultRelInfo, slot, estate, heap_tuple); } - ExecStoreHeapTuple(heap_tuple, entry->slot, false); - should_update = check_security_quals(entry->qualExprs, - entry->slot, - econtext); + if (shouldFree) + heap_freetuple(heap_tuple); } - /* Silently skip if USING policy filters out this row */ - if (should_update) + ExecDropSingleTupleTableSlot(index_slot); + index_endscan(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; + Oid relid = RelationGetRelid(resultRelInfo->ri_RelationDesc); + + /* 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; diff --git a/src/backend/executor/cypher_utils.c b/src/backend/executor/cypher_utils.c index 940284234..da66d47ef 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,68 @@ 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 = RelationGetPrimaryKeyIndex(rel, false); - /* - * If a single tuple was returned, the tuple is still valid, otherwise' - * set to false. - */ - if (!HeapTupleIsValid(tuple)) + if (!OidIsValid(index_oid)) + { + List *idx_list = RelationGetIndexList(rel); + ListCell *lc; + foreach(lc, idx_list) + { + Oid curr = lfirst_oid(lc); + Relation idx_rel = index_open(curr, AccessShareLock); + + if (idx_rel->rd_index->indisvalid && + idx_rel->rd_index->indnatts >= 1 && + idx_rel->rd_index->indkey.values[0] == 1) + { + index_oid = curr; + index_close(idx_rel, AccessShareLock); + break; + } + index_close(idx_rel, AccessShareLock); + } + list_free(idx_list); + } + + 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, 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)) + { + result = false; + } + + index_endscan(index_scan_desc); + index_close(index_rel, RowExclusiveLock); + 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 4e5b58632..0feeaba60 100644 --- a/src/backend/utils/adt/age_global_graph.c +++ b/src/backend/utils/adt/age_global_graph.c @@ -204,43 +204,139 @@ 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) + /* We look for 'ag_label_graph_oid_index' or any index starting with 'graph' */ + { + List *idx_list = RelationGetIndexList(ag_label); + ListCell *lc; + foreach(lc, idx_list) + { + Oid idx = lfirst_oid(lc); + Relation idx_rel = index_open(idx, AccessShareLock); + + /* + * Check if index is valid and the first key column is 'graph' (Anum_ag_label_graph). + * This matches 'ag_label_graph_oid_index'. + */ + if (idx_rel->rd_index->indisvalid && + idx_rel->rd_index->indnatts >= 1 && + idx_rel->rd_index->indkey.values[0] == Anum_ag_label_graph) + { + index_oid = idx; + index_close(idx_rel, AccessShareLock); + break; + } + index_close(idx_rel, AccessShareLock); + } + list_free(idx_list); + } + + if (OidIsValid(index_oid)) + { + Relation index_rel; + IndexScanDesc 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)); + + scan_desc = index_beginscan(ag_label, index_rel, snapshot, NULL, 1, 0); + index_rescan(scan_desc, &key, 1, NULL, 0); + + while (index_getnext_slot(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(scan_desc); + index_close(index_rel, AccessShareLock); + } else { - 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); + /* 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 c0c54e5a4..43b01cbcc 100644 --- a/src/backend/utils/adt/agtype.c +++ b/src/backend/utils/adt/agtype.c @@ -5624,11 +5624,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 +5649,74 @@ 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 = RelationGetPrimaryKeyIndex(graph_vertex_label, false); + + if (!OidIsValid(index_oid)) + { + List *idx_list = RelationGetIndexList(graph_vertex_label); + ListCell *lc; + foreach(lc, idx_list) + { + Oid curr = lfirst_oid(lc); + Relation idx_rel = index_open(curr, ShareLock); + + if (idx_rel->rd_index->indisvalid && + idx_rel->rd_index->indnatts >= 1 && + idx_rel->rd_index->indkey.values[0] == 1) + { + index_oid = curr; + index_close(idx_rel, ShareLock); + break; + } + index_close(idx_rel, ShareLock); + } + list_free(idx_list); + } + + 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_OIDEQ, + Int64GetDatum(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 +5726,11 @@ 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 +5755,13 @@ 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;