agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v3 3/3] fixups 152+ messages / 4 participants [nested] [flat]
* [PATCH v3 3/3] fixups @ 2018-02-28 23:20 Alvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Alvaro Herrera @ 2018-02-28 23:20 UTC (permalink / raw) --- src/backend/catalog/partition.c | 52 ++++++++++++---------- src/backend/executor/execPartition.c | 81 +++++++++++++--------------------- src/backend/optimizer/prep/prepunion.c | 59 ++++++++++++++++++++----- src/include/optimizer/prep.h | 15 +++---- 4 files changed, 115 insertions(+), 92 deletions(-) diff --git a/src/backend/catalog/partition.c b/src/backend/catalog/partition.c index 9d1ad09595..ef2ef3aa80 100644 --- a/src/backend/catalog/partition.c +++ b/src/backend/catalog/partition.c @@ -192,7 +192,7 @@ static int get_partition_bound_num_indexes(PartitionBoundInfo b); static int get_greatest_modulus(PartitionBoundInfo b); static uint64 compute_hash_value(int partnatts, FmgrInfo *partsupfunc, Datum *values, bool *isnull); -static Oid get_partition_parent_recurse(Oid relid, bool getroot); +static Oid get_partition_parent_recurse(Relation inhRel, Oid relid, bool getroot); /* * RelationBuildPartitionDesc @@ -1385,8 +1385,10 @@ check_default_allows_bound(Relation parent, Relation default_rel, /* * get_partition_parent + * Obtain direct parent or topmost ancestor of given relation * - * Returns inheritance parent of a partition by scanning pg_inherits + * Returns direct inheritance parent of a partition by scanning pg_inherits; + * or, if 'getroot' is true, the topmost parent in the inheritance hierarchy. * * Note: Because this function assumes that the relation whose OID is passed * as an argument will have precisely one parent, it should only be called @@ -1395,26 +1397,32 @@ check_default_allows_bound(Relation parent, Relation default_rel, Oid get_partition_parent(Oid relid, bool getroot) { - Oid parentOid = get_partition_parent_recurse(relid, getroot); + Relation inhRel; + Oid parentOid; + inhRel = heap_open(InheritsRelationId, AccessShareLock); + + parentOid = get_partition_parent_recurse(inhRel, relid, getroot); if (parentOid == InvalidOid) elog(ERROR, "could not find parent of relation %u", relid); + heap_close(inhRel, AccessShareLock); + return parentOid; } +/* + * get_partition_parent_recurse + * Recursive part of get_partition_parent + */ static Oid -get_partition_parent_recurse(Oid relid, bool getroot) +get_partition_parent_recurse(Relation inhRel, Oid relid, bool getroot) { - Form_pg_inherits form; - Relation catalogRelation; SysScanDesc scan; ScanKeyData key[2]; HeapTuple tuple; Oid result = InvalidOid; - catalogRelation = heap_open(InheritsRelationId, AccessShareLock); - ScanKeyInit(&key[0], Anum_pg_inherits_inhrelid, BTEqualStrategyNumber, F_OIDEQ, @@ -1424,28 +1432,26 @@ get_partition_parent_recurse(Oid relid, bool getroot) BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(1)); - scan = systable_beginscan(catalogRelation, InheritsRelidSeqnoIndexId, true, + /* Obtain the direct parent, and release resources before recursing */ + scan = systable_beginscan(inhRel, InheritsRelidSeqnoIndexId, true, NULL, 2, key); - tuple = systable_getnext(scan); if (HeapTupleIsValid(tuple)) - { - form = (Form_pg_inherits) GETSTRUCT(tuple); - result = form->inhparent; - - if (getroot) - result = get_partition_parent_recurse(result, getroot); - } - + result = ((Form_pg_inherits) GETSTRUCT(tuple))->inhparent; systable_endscan(scan); - heap_close(catalogRelation, AccessShareLock); /* - * If we recursed and got InvalidOid as parent, that means we reached the - * root of this partition tree in the form of 'relid' itself. + * If we were asked to recurse, do so now. Except that if we didn't get a + * valid parent, then the 'relid' argument was already the topmost parent, + * so return that. */ - if (getroot && !OidIsValid(result)) - return relid; + if (getroot) + { + if (OidIsValid(result)) + return get_partition_parent_recurse(inhRel, result, getroot); + else + return relid; + } return result; } diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index 3f7b61dc37..7ea0295d3c 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -65,6 +65,7 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel) int num_update_rri = 0, update_rri_index = 0; PartitionTupleRouting *proute; + int nparts; /* * Get the information about the partition tree after locking all the @@ -75,14 +76,12 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel) proute->partition_dispatch_info = RelationGetPartitionDispatchInfo(rel, &proute->num_dispatch, &leaf_parts); - proute->num_partitions = list_length(leaf_parts); - proute->partitions = (ResultRelInfo **) palloc(proute->num_partitions * - sizeof(ResultRelInfo *)); + proute->num_partitions = nparts = list_length(leaf_parts); + proute->partitions = + (ResultRelInfo **) palloc(nparts * sizeof(ResultRelInfo *)); proute->parent_child_tupconv_maps = - (TupleConversionMap **) palloc0(proute->num_partitions * - sizeof(TupleConversionMap *)); - proute->partition_oids = (Oid *) palloc(proute->num_partitions * - sizeof(Oid)); + (TupleConversionMap **) palloc0(nparts * sizeof(TupleConversionMap *)); + proute->partition_oids = (Oid *) palloc(nparts * sizeof(Oid)); /* Set up details specific to the type of tuple routing we are doing. */ if (mtstate && mtstate->operation == CMD_UPDATE) @@ -116,15 +115,12 @@ ExecSetupPartitionTupleRouting(ModifyTableState *mtstate, Relation rel) */ if (mtstate && mtstate->mt_onconflict != ONCONFLICT_NONE) { - proute->partition_arbiter_indexes = (List **) - palloc(proute->num_partitions * - sizeof(List *)); - proute->partition_conflproj_slots = (TupleTableSlot **) - palloc(proute->num_partitions * - sizeof(TupleTableSlot *)); - proute->partition_existing_slots = (TupleTableSlot **) - palloc(proute->num_partitions * - sizeof(TupleTableSlot *)); + proute->partition_arbiter_indexes = + (List **) palloc(nparts * sizeof(List *)); + proute->partition_conflproj_slots = + (TupleTableSlot **) palloc(nparts * sizeof(TupleTableSlot *)); + proute->partition_existing_slots = + (TupleTableSlot **) palloc(nparts * sizeof(TupleTableSlot *)); } i = 0; @@ -537,48 +533,33 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, { /* Convert expressions contain partition's attnos. */ List *conv_setproj; - AppendRelInfo appinfo; TupleDesc tupDesc; /* Need our own slot. */ part_existing_slot = ExecInitExtraTupleSlot(mtstate->ps.state, partrelDesc); - /* First convert references to EXCLUDED pseudo-relation. */ - conv_setproj = map_partition_varattnos((List *) - node->onConflictSet, - INNER_VAR, - partrel, - firstResultRel, NULL); + /* + * First convert references to the EXCLUDED pseudo-relation, which + * was set to INNER_VAR by set_plan_references. + */ + conv_setproj = + map_partition_varattnos((List *) node->onConflictSet, + INNER_VAR, partrel, + firstResultRel, NULL); + /* Then convert references to main target relation. */ - conv_setproj = map_partition_varattnos((List *) - conv_setproj, - firstVarno, - partrel, - firstResultRel, NULL); + conv_setproj = + map_partition_varattnos((List *) conv_setproj, + firstVarno, partrel, + firstResultRel, NULL); - /* - * Need to fix the target entries' resnos too by using - * inheritance translation. - */ - appinfo.type = T_AppendRelInfo; - appinfo.parent_relid = firstVarno; - appinfo.parent_reltype = firstResultRel->rd_rel->reltype; - appinfo.child_relid = partrel->rd_id; - appinfo.child_reltype = partrel->rd_rel->reltype; - appinfo.parent_reloid = firstResultRel->rd_id; - make_inh_translation_list(firstResultRel, partrel, - 1, /* dummy */ - &appinfo.translated_vars); - conv_setproj = adjust_inherited_tlist((List *) conv_setproj, - &appinfo); - - /* - * Add any attributes that are missing in the source list, such - * as, dropped columns in the partition. - */ - conv_setproj = expand_targetlist(conv_setproj, CMD_UPDATE, - firstVarno, partrel); + conv_setproj = + adjust_and_expand_partition_tlist(RelationGetDescr(firstResultRel), + RelationGetDescr(partrel), + RelationGetRelationName(partrel), + firstVarno, + conv_setproj); tupDesc = ExecTypeFromTL(conv_setproj, partrelDesc->tdhasoid); part_conflproj_slot = ExecInitExtraTupleSlot(mtstate->ps.state, diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c index 4153891f29..c11f6c20ab 100644 --- a/src/backend/optimizer/prep/prepunion.c +++ b/src/backend/optimizer/prep/prepunion.c @@ -124,6 +124,8 @@ static Node *adjust_appendrel_attrs_mutator(Node *node, adjust_appendrel_attrs_context *context); static Relids adjust_child_relids(Relids relids, int nappinfos, AppendRelInfo **appinfos); +static List *adjust_inherited_tlist(List *tlist, + AppendRelInfo *context); /* @@ -2357,7 +2359,7 @@ adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, * * Note that this is not needed for INSERT because INSERT isn't inheritable. */ -List * +static List * adjust_inherited_tlist(List *tlist, AppendRelInfo *context) { bool changed_it = false; @@ -2379,8 +2381,10 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context) continue; /* ignore junk items */ /* - * ignore dummy tlist entry added by exapnd_targetlist() for - * dropped columns in the parent table. + * XXX ugly hack: must ignore dummy tlist entry added by + * expand_targetlist() for dropped columns in the parent table or we + * fail because there is no translation. Must find a better way to + * deal with this case, though. */ if (IsA(tle->expr, Const) && ((Const *) tle->expr)->constisnull) continue; @@ -2423,10 +2427,7 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context) if (tle->resjunk) continue; /* ignore junk items */ - /* - * ignore dummy tlist entry added by exapnd_targetlist() for - * dropped columns in the parent table. - */ + /* XXX ugly hack; see above */ if (IsA(tle->expr, Const) && ((Const *) tle->expr)->constisnull) continue; @@ -2444,10 +2445,7 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context) if (!tle->resjunk) continue; /* here, ignore non-junk items */ - /* - * ignore dummy tlist entry added by exapnd_targetlist() for - * dropped columns in the parent table. - */ + /* XXX ugly hack; see above */ if (IsA(tle->expr, Const) && ((Const *) tle->expr)->constisnull) continue; @@ -2460,6 +2458,45 @@ adjust_inherited_tlist(List *tlist, AppendRelInfo *context) } /* + * Given a targetlist for the parentRel of the given varno, adjust it to be in + * the correct order and to contain all the needed elements for the given + * partition. + */ +List * +adjust_and_expand_partition_tlist(TupleDesc parentDesc, + TupleDesc partitionDesc, + char *partitionRelname, + int parentVarno, + List *targetlist) +{ + AppendRelInfo appinfo; + List *result_tl; + + /* + * Fist, fix the target entries' resnos, by using inheritance translation. + */ + appinfo.type = T_AppendRelInfo; + appinfo.parent_relid = parentVarno; + appinfo.parent_reltype = InvalidOid; // parentRel->rd_rel->reltype; + appinfo.child_relid = -1; + appinfo.child_reltype = InvalidOid; // partrel->rd_rel->reltype; + appinfo.parent_reloid = 1; // dummy parentRel->rd_id; + make_inh_translation_list(parentDesc, partitionDesc, partitionRelname, + 1, /* dummy */ + &appinfo.translated_vars); + result_tl = adjust_inherited_tlist((List *) targetlist, &appinfo); + + /* + * Add any attributes that are missing in the source list, such + * as dropped columns in the partition. + */ + result_tl = expand_targetlist(result_tl, CMD_UPDATE, + parentVarno, partitionDesc); + + return result_tl; +} + +/* * adjust_appendrel_attrs_multilevel * Apply Var translations from a toplevel appendrel parent down to a child. * diff --git a/src/include/optimizer/prep.h b/src/include/optimizer/prep.h index d380b419d7..c5263f65dc 100644 --- a/src/include/optimizer/prep.h +++ b/src/include/optimizer/prep.h @@ -14,6 +14,7 @@ #ifndef PREP_H #define PREP_H +#include "access/tupdesc.h" #include "nodes/plannodes.h" #include "nodes/relation.h" @@ -42,9 +43,8 @@ extern List *preprocess_targetlist(PlannerInfo *root); extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex); -typedef struct RelationData *Relation; extern List *expand_targetlist(List *tlist, int command_type, - Index result_relation, Relation rel); + Index result_relation, TupleDesc tupdesc); /* * prototypes for prepunion.c @@ -69,11 +69,10 @@ extern SpecialJoinInfo *build_child_join_sjinfo(PlannerInfo *root, extern Relids adjust_child_relids_multilevel(PlannerInfo *root, Relids relids, Relids child_relids, Relids top_parent_relids); -extern void make_inh_translation_list(Relation oldrelation, - Relation newrelation, - Index newvarno, - List **translated_vars); -extern List *adjust_inherited_tlist(List *tlist, - AppendRelInfo *context); +extern List *adjust_and_expand_partition_tlist(TupleDesc parentDesc, + TupleDesc partitionDesc, + char *partitionRelname, + int parentVarno, + List *targetlist); #endif /* PREP_H */ -- 2.11.0 --l3nzpdtx3xgmrx2w-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v9 2/3] Dedicated memory context for hash join spill buffers @ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Jehan-Guillaume de Rorthais @ 2023-05-16 13:42 UTC (permalink / raw) Should a hash join exceed work_mem, its hashtable is split up into multiple batches. The number of batches is doubled each time a given batch is determined not to fit in memory. Each batch file is allocated with a block-sized buffer for buffering tuples and parallel hash join has additional sharedtuplestore accessor buffers. In some pathological cases requiring a lot of batches, often with skewed data, bad stats, or very large datasets, users can run out-of-memory solely from the memory overhead of all the batch files' buffers. Batch files were allocated in the ExecutorState memory context, making it very hard to identify when this batch explosion was the source of an OOM. By allocating the batch files in a dedicated memory context, it should be easier for users to identify the cause of an OOM and work to avoid it. Original draft by Tomas Vondra. Author: Tomas Vondra <[email protected]> Author: Jehan-Guillaume de Rorthais <[email protected]> Reviewed-by: Melanie Plageman <[email protected]> Discussion: https://postgr.es/m/20190421114618.z3mpgmimc3rmubi4@development Discussion: https://postgr.es/m/20230504193006.1b5b9622%40karst#273020ff4061fc7a2fbb1ba96b281f17 --- src/backend/executor/nodeHash.c | 43 ++++++++++++++++------- src/backend/executor/nodeHashjoin.c | 31 ++++++++++++---- src/backend/utils/sort/sharedtuplestore.c | 8 +++++ src/include/executor/hashjoin.h | 30 +++++++++++----- src/include/executor/nodeHashjoin.h | 2 +- 5 files changed, 84 insertions(+), 30 deletions(-) diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index 5fd1c5553b..444d182bca 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -484,7 +484,7 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, * * The hashtable control block is just palloc'd from the executor's * per-query memory context. Everything else should be kept inside the - * subsidiary hashCxt or batchCxt. + * subsidiary hashCxt, batchCxt or spillCxt. */ hashtable = palloc_object(HashJoinTableData); hashtable->nbuckets = nbuckets; @@ -538,6 +538,10 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, "HashBatchContext", ALLOCSET_DEFAULT_SIZES); + hashtable->spillCxt = AllocSetContextCreate(hashtable->hashCxt, + "HashSpillContext", + ALLOCSET_DEFAULT_SIZES); + /* Allocate data that will live for the life of the hashjoin */ oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); @@ -570,12 +574,19 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, if (nbatch > 1 && hashtable->parallel_state == NULL) { + MemoryContext oldctx; + /* * allocate and initialize the file arrays in hashCxt (not needed for * parallel case which uses shared tuplestores instead of raw files) */ + oldctx = MemoryContextSwitchTo(hashtable->spillCxt); + hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch); hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch); + + MemoryContextSwitchTo(oldctx); + /* The files will not be opened until needed... */ /* ... but make sure we have temp tablespaces established for them */ PrepareTempTablespaces(); @@ -913,7 +924,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) int oldnbatch = hashtable->nbatch; int curbatch = hashtable->curbatch; int nbatch; - MemoryContext oldcxt; long ninmemory; long nfreed; HashMemoryChunk oldchunks; @@ -934,13 +944,16 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) hashtable, nbatch, hashtable->spaceUsed); #endif - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); - if (hashtable->innerBatchFile == NULL) { + MemoryContext oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); + /* we had no file arrays before */ hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch); hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch); + + MemoryContextSwitchTo(oldcxt); + /* time to establish the temp tablespaces, too */ PrepareTempTablespaces(); } @@ -951,8 +964,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) hashtable->outerBatchFile = repalloc0_array(hashtable->outerBatchFile, BufFile *, oldnbatch, nbatch); } - MemoryContextSwitchTo(oldcxt); - hashtable->nbatch = nbatch; /* @@ -1024,7 +1035,8 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) Assert(batchno > curbatch); ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(hashTuple), hashTuple->hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); hashtable->spaceUsed -= hashTupleSize; nfreed++; @@ -1683,7 +1695,8 @@ ExecHashTableInsert(HashJoinTable hashtable, Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(tuple, hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); } if (shouldFree) @@ -2664,7 +2677,8 @@ ExecHashRemoveNextSkewBucket(HashJoinTable hashtable) /* Put the tuple into a temp file for later batches */ Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(tuple, hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); pfree(hashTuple); hashtable->spaceUsed -= tupleSize; hashtable->spaceUsedSkew -= tupleSize; @@ -3093,8 +3107,11 @@ ExecParallelHashJoinSetUpBatches(HashJoinTable hashtable, int nbatch) pstate->nbatch = nbatch; batches = dsa_get_address(hashtable->area, pstate->batches); - /* Use hash join memory context. */ - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); + /* + * Use hash join spill memory context to allocate accessors and their + * buffers. + */ + oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); /* Allocate this backend's accessor array. */ hashtable->nbatch = nbatch; @@ -3196,8 +3213,8 @@ ExecParallelHashEnsureBatchAccessors(HashJoinTable hashtable) */ Assert(DsaPointerIsValid(pstate->batches)); - /* Use hash join memory context. */ - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); + /* Use hash join spill memory context to allocate accessors. */ + oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); /* Allocate this backend's accessor array. */ hashtable->nbatch = pstate->nbatch; diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 78e202b4f9..1092a33525 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -489,7 +489,8 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel) Assert(parallel_state == NULL); Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(mintuple, hashvalue, - &hashtable->outerBatchFile[batchno]); + &hashtable->outerBatchFile[batchno], + hashtable); if (shouldFree) heap_free_minimal_tuple(mintuple); @@ -1310,22 +1311,38 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate) * * The data recorded in the file for each tuple is its hash value, * then the tuple in MinimalTuple format. - * - * Note: it is important always to call this in the regular executor - * context, not in a shorter-lived context; else the temp file buffers - * will get messed up. */ void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue, - BufFile **fileptr) + BufFile **fileptr, HashJoinTable hashtable) { BufFile *file = *fileptr; if (file == NULL) { - /* First write to this batch file, so open it. */ + MemoryContext oldctx; + + /* + * The batch file is lazily created. If this is the first tuple + * written to this batch, the batch file is created and its buffer is + * allocated in the spillCxt context, NOT in the batchCxt. + * + * During the building phase, inner batch are created with their temp + * file buffers. These buffers are released later, after the batch is + * loaded back to memory during the outer side scan. That explains why + * it is important to use a memory context which live longer than the + * batch itself or some temp file buffers will get messed up. + * + * Also, we use spillCxt instead of hashCxt for a better accounting of + * the spilling memory consumption. + */ + + oldctx = MemoryContextSwitchTo(hashtable->spillCxt); + file = BufFileCreateTemp(false); *fileptr = file; + + MemoryContextSwitchTo(oldctx); } BufFileWrite(file, &hashvalue, sizeof(uint32)); diff --git a/src/backend/utils/sort/sharedtuplestore.c b/src/backend/utils/sort/sharedtuplestore.c index 0831249159..236be65f22 100644 --- a/src/backend/utils/sort/sharedtuplestore.c +++ b/src/backend/utils/sort/sharedtuplestore.c @@ -308,11 +308,15 @@ sts_puttuple(SharedTuplestoreAccessor *accessor, void *meta_data, { SharedTuplestoreParticipant *participant; char name[MAXPGPATH]; + MemoryContext oldcxt; /* Create one. Only this backend will write into it. */ sts_filename(name, accessor, accessor->participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); accessor->write_file = BufFileCreateFileSet(&accessor->fileset->fs, name); + MemoryContextSwitchTo(oldcxt); /* Set up the shared state for this backend's file. */ participant = &accessor->sts->participants[accessor->participant]; @@ -527,11 +531,15 @@ sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data) if (accessor->read_file == NULL) { char name[MAXPGPATH]; + MemoryContext oldcxt; sts_filename(name, accessor, accessor->read_participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); accessor->read_file = BufFileOpenFileSet(&accessor->fileset->fs, name, O_RDONLY, false); + MemoryContextSwitchTo(oldcxt); } /* Seek and load the chunk header. */ diff --git a/src/include/executor/hashjoin.h b/src/include/executor/hashjoin.h index 8ee59d2c71..857ca58f6f 100644 --- a/src/include/executor/hashjoin.h +++ b/src/include/executor/hashjoin.h @@ -23,12 +23,12 @@ /* ---------------------------------------------------------------- * hash-join hash table structures * - * Each active hashjoin has a HashJoinTable control block, which is - * palloc'd in the executor's per-query context. All other storage needed - * for the hashjoin is kept in private memory contexts, two for each hashjoin. - * This makes it easy and fast to release the storage when we don't need it - * anymore. (Exception: data associated with the temp files lives in the - * per-query context too, since we always call buffile.c in that context.) + * Each active hashjoin has a HashJoinTable structure, which is + * palloc'd in the executor's per-query context. Other storage needed for + * each hashjoin is kept in child contexts, three for each hashjoin: + * - HashTableContext (hashCxt): the parent hash table storage context + * - HashSpillContext (spillCxt): storage for temp files buffers + * - HashBatchContext (batchCxt): storage for a batch in serial hash join * * The hashtable contexts are made children of the per-query context, ensuring * that they will be discarded at end of statement even if the join is @@ -36,9 +36,20 @@ * be cleaned up by the virtual file manager in event of an error.) * * Storage that should live through the entire join is allocated from the - * "hashCxt", while storage that is only wanted for the current batch is - * allocated in the "batchCxt". By resetting the batchCxt at the end of - * each batch, we free all the per-batch storage reliably and without tedium. + * "hashCxt" (mainly the hashtable's metadata). Also, the "hashCxt" context is + * the parent of "spillCxt" and "batchCxt". It makes it easy and fast to + * release the storage when we don't need it anymore. + * + * Data associated with temp files is allocated in the "spillCxt" context + * which lives for the duration of the entire join as batch files' + * creation and usage may span batch execution. These files are + * explicitly destroyed by calling BufFileClose() when the code is done + * with them. The aim of this context is to help accounting for the + * memory allocated for temp files and their buffers. + * + * Finally, data used only during a single batch's execution is allocated + * in the "batchCxt". By resetting the batchCxt at the end of each batch, + * we free all the per-batch storage reliably and without tedium. * * During first scan of inner relation, we get its tuples from executor. * If nbatch > 1 then tuples that don't belong in first batch get saved @@ -350,6 +361,7 @@ typedef struct HashJoinTableData MemoryContext hashCxt; /* context for whole-hash-join storage */ MemoryContext batchCxt; /* context for this-batch-only storage */ + MemoryContext spillCxt; /* context for spilling to temp files */ /* used for dense allocation of tuples (into linked chunks) */ HashMemoryChunk chunks; /* one list for the whole batch */ diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h index d367070883..ccb704ede1 100644 --- a/src/include/executor/nodeHashjoin.h +++ b/src/include/executor/nodeHashjoin.h @@ -29,6 +29,6 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state, ParallelWorkerContext *pwcxt); extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue, - BufFile **fileptr); + BufFile **fileptr, HashJoinTable hashtable); #endif /* NODEHASHJOIN_H */ -- 2.40.1 --MP_/CAw=Cm.TBs/NMHAJ6DWYJQ5 Content-Type: text/x-patch Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=v9-0003-Run-pgindent-on-nodeHash.c-and-nodeHashjoin.c.patch ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v10 3/3] Dedicated memory context for hash join spill buffers @ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Jehan-Guillaume de Rorthais @ 2023-05-16 13:42 UTC (permalink / raw) Should a hash join exceed work_mem, its hashtable is split up into multiple batches. The number of batches is doubled each time a given batch is determined not to fit in memory. Each batch file is allocated with a block-sized buffer for buffering tuples and parallel hash join has additional sharedtuplestore accessor buffers. In some pathological cases requiring a lot of batches, often with skewed data, bad stats, or very large datasets, users can run out-of-memory solely from the memory overhead of all the batch files' buffers. Batch files were allocated in the ExecutorState memory context, making it very hard to identify when this batch explosion was the source of an OOM. By allocating the batch files in a dedicated memory context, it should be easier for users to identify the cause of an OOM and work to avoid it. Original draft by Tomas Vondra. Author: Tomas Vondra <[email protected]> Author: Jehan-Guillaume de Rorthais <[email protected]> Reviewed-by: Melanie Plageman <[email protected]> Discussion: https://postgr.es/m/20190421114618.z3mpgmimc3rmubi4@development Discussion: https://postgr.es/m/20230504193006.1b5b9622%40karst#273020ff4061fc7a2fbb1ba96b281f17 --- src/backend/executor/nodeHash.c | 43 ++++++++++++++++------- src/backend/executor/nodeHashjoin.c | 32 +++++++++++++---- src/backend/utils/sort/sharedtuplestore.c | 8 +++++ src/include/executor/hashjoin.h | 30 +++++++++++----- src/include/executor/nodeHashjoin.h | 2 +- 5 files changed, 86 insertions(+), 29 deletions(-) diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c index ac3eb32d97..7571db4c1d 100644 --- a/src/backend/executor/nodeHash.c +++ b/src/backend/executor/nodeHash.c @@ -484,7 +484,7 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, * * The hashtable control block is just palloc'd from the executor's * per-query memory context. Everything else should be kept inside the - * subsidiary hashCxt or batchCxt. + * subsidiary hashCxt, batchCxt or spillCxt. */ hashtable = palloc_object(HashJoinTableData); hashtable->nbuckets = nbuckets; @@ -538,6 +538,10 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, "HashBatchContext", ALLOCSET_DEFAULT_SIZES); + hashtable->spillCxt = AllocSetContextCreate(hashtable->hashCxt, + "HashSpillContext", + ALLOCSET_DEFAULT_SIZES); + /* Allocate data that will live for the life of the hashjoin */ oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); @@ -570,12 +574,19 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations, if (nbatch > 1 && hashtable->parallel_state == NULL) { + MemoryContext oldctx; + /* * allocate and initialize the file arrays in hashCxt (not needed for * parallel case which uses shared tuplestores instead of raw files) */ + oldctx = MemoryContextSwitchTo(hashtable->spillCxt); + hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch); hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch); + + MemoryContextSwitchTo(oldctx); + /* The files will not be opened until needed... */ /* ... but make sure we have temp tablespaces established for them */ PrepareTempTablespaces(); @@ -913,7 +924,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) int oldnbatch = hashtable->nbatch; int curbatch = hashtable->curbatch; int nbatch; - MemoryContext oldcxt; long ninmemory; long nfreed; HashMemoryChunk oldchunks; @@ -934,13 +944,16 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) hashtable, nbatch, hashtable->spaceUsed); #endif - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); - if (hashtable->innerBatchFile == NULL) { + MemoryContext oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); + /* we had no file arrays before */ hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch); hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch); + + MemoryContextSwitchTo(oldcxt); + /* time to establish the temp tablespaces, too */ PrepareTempTablespaces(); } @@ -951,8 +964,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) hashtable->outerBatchFile = repalloc0_array(hashtable->outerBatchFile, BufFile *, oldnbatch, nbatch); } - MemoryContextSwitchTo(oldcxt); - hashtable->nbatch = nbatch; /* @@ -1024,7 +1035,8 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable) Assert(batchno > curbatch); ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(hashTuple), hashTuple->hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); hashtable->spaceUsed -= hashTupleSize; nfreed++; @@ -1683,7 +1695,8 @@ ExecHashTableInsert(HashJoinTable hashtable, Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(tuple, hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); } if (shouldFree) @@ -2664,7 +2677,8 @@ ExecHashRemoveNextSkewBucket(HashJoinTable hashtable) /* Put the tuple into a temp file for later batches */ Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(tuple, hashvalue, - &hashtable->innerBatchFile[batchno]); + &hashtable->innerBatchFile[batchno], + hashtable); pfree(hashTuple); hashtable->spaceUsed -= tupleSize; hashtable->spaceUsedSkew -= tupleSize; @@ -3093,8 +3107,11 @@ ExecParallelHashJoinSetUpBatches(HashJoinTable hashtable, int nbatch) pstate->nbatch = nbatch; batches = dsa_get_address(hashtable->area, pstate->batches); - /* Use hash join memory context. */ - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); + /* + * Use hash join spill memory context to allocate accessors and their + * buffers. + */ + oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); /* Allocate this backend's accessor array. */ hashtable->nbatch = nbatch; @@ -3196,8 +3213,8 @@ ExecParallelHashEnsureBatchAccessors(HashJoinTable hashtable) */ Assert(DsaPointerIsValid(pstate->batches)); - /* Use hash join memory context. */ - oldcxt = MemoryContextSwitchTo(hashtable->hashCxt); + /* Use hash join spill memory context to allocate accessors. */ + oldcxt = MemoryContextSwitchTo(hashtable->spillCxt); /* Allocate this backend's accessor array. */ hashtable->nbatch = pstate->nbatch; diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index 35b005a6a4..42a3c64fb9 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -489,7 +489,8 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel) Assert(parallel_state == NULL); Assert(batchno > hashtable->curbatch); ExecHashJoinSaveTuple(mintuple, hashvalue, - &hashtable->outerBatchFile[batchno]); + &hashtable->outerBatchFile[batchno], + hashtable); if (shouldFree) heap_free_minimal_tuple(mintuple); @@ -1311,21 +1312,40 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate) * The data recorded in the file for each tuple is its hash value, * then the tuple in MinimalTuple format. * - * Note: it is important always to call this in the regular executor - * context, not in a shorter-lived context; else the temp file buffers - * will get messed up. + * fileptr points to either an inner or outer batch file inside the hashtable + * arrays. */ void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue, - BufFile **fileptr) + BufFile **fileptr, HashJoinTable hashtable) { BufFile *file = *fileptr; if (file == NULL) { - /* First write to this batch file, so open it. */ + MemoryContext oldctx; + + /* + * The batch file is lazily created. If this is the first tuple + * written to this batch, the batch file is created and its buffer is + * allocated in the spillCxt context, NOT in the batchCxt. + * + * During the build phase, buffered files are created for inner + * batches. Each batch's buffered file is closed (and its buffer freed) + * after the batch is loaded into memory during the outer side scan. + * Therefore, it is necessary to allocate the batch file buffer in a + * memory context which outlives the batch itself. + * + * Also, we use spillCxt instead of hashCxt for a better accounting of + * the spilling memory consumption. + */ + + oldctx = MemoryContextSwitchTo(hashtable->spillCxt); + file = BufFileCreateTemp(false); *fileptr = file; + + MemoryContextSwitchTo(oldctx); } BufFileWrite(file, &hashvalue, sizeof(uint32)); diff --git a/src/backend/utils/sort/sharedtuplestore.c b/src/backend/utils/sort/sharedtuplestore.c index 0831249159..236be65f22 100644 --- a/src/backend/utils/sort/sharedtuplestore.c +++ b/src/backend/utils/sort/sharedtuplestore.c @@ -308,11 +308,15 @@ sts_puttuple(SharedTuplestoreAccessor *accessor, void *meta_data, { SharedTuplestoreParticipant *participant; char name[MAXPGPATH]; + MemoryContext oldcxt; /* Create one. Only this backend will write into it. */ sts_filename(name, accessor, accessor->participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); accessor->write_file = BufFileCreateFileSet(&accessor->fileset->fs, name); + MemoryContextSwitchTo(oldcxt); /* Set up the shared state for this backend's file. */ participant = &accessor->sts->participants[accessor->participant]; @@ -527,11 +531,15 @@ sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data) if (accessor->read_file == NULL) { char name[MAXPGPATH]; + MemoryContext oldcxt; sts_filename(name, accessor, accessor->read_participant); + + oldcxt = MemoryContextSwitchTo(accessor->context); accessor->read_file = BufFileOpenFileSet(&accessor->fileset->fs, name, O_RDONLY, false); + MemoryContextSwitchTo(oldcxt); } /* Seek and load the chunk header. */ diff --git a/src/include/executor/hashjoin.h b/src/include/executor/hashjoin.h index 8ee59d2c71..857ca58f6f 100644 --- a/src/include/executor/hashjoin.h +++ b/src/include/executor/hashjoin.h @@ -23,12 +23,12 @@ /* ---------------------------------------------------------------- * hash-join hash table structures * - * Each active hashjoin has a HashJoinTable control block, which is - * palloc'd in the executor's per-query context. All other storage needed - * for the hashjoin is kept in private memory contexts, two for each hashjoin. - * This makes it easy and fast to release the storage when we don't need it - * anymore. (Exception: data associated with the temp files lives in the - * per-query context too, since we always call buffile.c in that context.) + * Each active hashjoin has a HashJoinTable structure, which is + * palloc'd in the executor's per-query context. Other storage needed for + * each hashjoin is kept in child contexts, three for each hashjoin: + * - HashTableContext (hashCxt): the parent hash table storage context + * - HashSpillContext (spillCxt): storage for temp files buffers + * - HashBatchContext (batchCxt): storage for a batch in serial hash join * * The hashtable contexts are made children of the per-query context, ensuring * that they will be discarded at end of statement even if the join is @@ -36,9 +36,20 @@ * be cleaned up by the virtual file manager in event of an error.) * * Storage that should live through the entire join is allocated from the - * "hashCxt", while storage that is only wanted for the current batch is - * allocated in the "batchCxt". By resetting the batchCxt at the end of - * each batch, we free all the per-batch storage reliably and without tedium. + * "hashCxt" (mainly the hashtable's metadata). Also, the "hashCxt" context is + * the parent of "spillCxt" and "batchCxt". It makes it easy and fast to + * release the storage when we don't need it anymore. + * + * Data associated with temp files is allocated in the "spillCxt" context + * which lives for the duration of the entire join as batch files' + * creation and usage may span batch execution. These files are + * explicitly destroyed by calling BufFileClose() when the code is done + * with them. The aim of this context is to help accounting for the + * memory allocated for temp files and their buffers. + * + * Finally, data used only during a single batch's execution is allocated + * in the "batchCxt". By resetting the batchCxt at the end of each batch, + * we free all the per-batch storage reliably and without tedium. * * During first scan of inner relation, we get its tuples from executor. * If nbatch > 1 then tuples that don't belong in first batch get saved @@ -350,6 +361,7 @@ typedef struct HashJoinTableData MemoryContext hashCxt; /* context for whole-hash-join storage */ MemoryContext batchCxt; /* context for this-batch-only storage */ + MemoryContext spillCxt; /* context for spilling to temp files */ /* used for dense allocation of tuples (into linked chunks) */ HashMemoryChunk chunks; /* one list for the whole batch */ diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h index d367070883..ccb704ede1 100644 --- a/src/include/executor/nodeHashjoin.h +++ b/src/include/executor/nodeHashjoin.h @@ -29,6 +29,6 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state, ParallelWorkerContext *pwcxt); extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue, - BufFile **fileptr); + BufFile **fileptr, HashJoinTable hashtable); #endif /* NODEHASHJOIN_H */ -- 2.40.1 --MP_/zjnH27gFIT.OObnMRRKM4.=-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v1] Provide relfilenode statistics @ 2023-11-16 02:30 Bertrand Drouvot <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Bertrand Drouvot @ 2023-11-16 02:30 UTC (permalink / raw) --- src/backend/access/rmgrdesc/xactdesc.c | 5 +- src/backend/catalog/storage.c | 8 ++ src/backend/catalog/system_functions.sql | 2 +- src/backend/catalog/system_views.sql | 5 +- src/backend/postmaster/checkpointer.c | 5 + src/backend/storage/buffer/bufmgr.c | 6 +- src/backend/storage/smgr/md.c | 7 ++ src/backend/utils/activity/pgstat.c | 39 ++++-- src/backend/utils/activity/pgstat_database.c | 12 +- src/backend/utils/activity/pgstat_function.c | 13 +- src/backend/utils/activity/pgstat_relation.c | 112 ++++++++++++++++-- src/backend/utils/activity/pgstat_replslot.c | 13 +- src/backend/utils/activity/pgstat_shmem.c | 19 ++- .../utils/activity/pgstat_subscription.c | 12 +- src/backend/utils/activity/pgstat_xact.c | 60 +++++++--- src/backend/utils/adt/pgstatfuncs.c | 34 +++++- src/include/access/tableam.h | 19 +++ src/include/access/xact.h | 1 + src/include/catalog/pg_proc.dat | 14 ++- src/include/pgstat.h | 19 ++- src/include/utils/pgstat_internal.h | 34 ++++-- src/test/recovery/t/029_stats_restart.pl | 40 +++---- .../recovery/t/030_stats_cleanup_replica.pl | 6 +- src/test/regress/expected/rules.out | 12 +- src/test/regress/expected/stats.out | 30 ++--- src/test/regress/sql/stats.sql | 30 ++--- src/test/subscription/t/026_stats.pl | 4 +- src/tools/pgindent/typedefs.list | 1 + 28 files changed, 415 insertions(+), 147 deletions(-) 4.6% src/backend/catalog/ 47.8% src/backend/utils/activity/ 6.5% src/backend/utils/adt/ 3.7% src/backend/ 3.3% src/include/access/ 3.3% src/include/catalog/ 6.2% src/include/utils/ 3.3% src/include/ 12.1% src/test/recovery/t/ 5.5% src/test/regress/expected/ 3.0% src/test/ diff --git a/src/backend/access/rmgrdesc/xactdesc.c b/src/backend/access/rmgrdesc/xactdesc.c index dccca201e0..c02b079645 100644 --- a/src/backend/access/rmgrdesc/xactdesc.c +++ b/src/backend/access/rmgrdesc/xactdesc.c @@ -319,10 +319,11 @@ xact_desc_stats(StringInfo buf, const char *label, appendStringInfo(buf, "; %sdropped stats:", label); for (i = 0; i < ndropped; i++) { - appendStringInfo(buf, " %d/%u/%u", + appendStringInfo(buf, " %d/%u/%u/%u", dropped_stats[i].kind, dropped_stats[i].dboid, - dropped_stats[i].objoid); + dropped_stats[i].objoid, + dropped_stats[i].relfile); } } } diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index f56b3cc0f2..db6107cd90 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -33,6 +33,7 @@ #include "storage/smgr.h" #include "utils/hsearch.h" #include "utils/memutils.h" +#include "utils/pgstat_internal.h" #include "utils/rel.h" /* GUC variables */ @@ -152,6 +153,7 @@ RelationCreateStorage(RelFileLocator rlocator, char relpersistence, if (needs_wal) log_smgrcreate(&srel->smgr_rlocator.locator, MAIN_FORKNUM); + pgstat_create_transactional(PGSTAT_KIND_RELFILENODE, rlocator.dbOid, rlocator.spcOid, rlocator.relNumber); /* * Add the relation to the list of stuff to delete at abort, if we are * asked to do so. @@ -227,6 +229,8 @@ RelationDropStorage(Relation rel) * for now I'll keep the logic simple. */ + pgstat_drop_transactional(PGSTAT_KIND_RELFILENODE, rel->rd_locator.dbOid, rel->rd_locator.spcOid, rel->rd_locator.relNumber); + RelationCloseSmgr(rel); } @@ -253,6 +257,9 @@ RelationPreserveStorage(RelFileLocator rlocator, bool atCommit) PendingRelDelete *pending; PendingRelDelete *prev; PendingRelDelete *next; + PgStat_SubXactStatus *xact_state; + + xact_state = pgStatXactStack; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -267,6 +274,7 @@ RelationPreserveStorage(RelFileLocator rlocator, bool atCommit) else pendingDeletes = next; pfree(pending); + PgStat_RemoveRelFileNodeFromDroppedStats(xact_state, rlocator); /* prev does not change */ } else diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index ae099e328c..140c8d556c 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -681,7 +681,7 @@ REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM publ REVOKE EXECUTE ON FUNCTION pg_stat_reset_replication_slot(text) FROM public; -REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, oid) FROM public; +REVOKE EXECUTE ON FUNCTION pg_stat_have_stats(text, oid, oid, oid) FROM public; REVOKE EXECUTE ON FUNCTION pg_stat_reset_subscription_stats(oid) FROM public; diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 53047cab5f..b0d7af6df0 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -750,6 +750,7 @@ CREATE VIEW pg_statio_all_tables AS C.relname AS relname, pg_stat_get_blocks_fetched(C.oid) - pg_stat_get_blocks_hit(C.oid) AS heap_blks_read, + pg_stat_get_blocks_written(C.oid) + pg_stat_get_relfilenode_blocks_written(d.oid, CASE WHEN C.reltablespace <> 0 THEN C.reltablespace ELSE d.dattablespace END, C.relfilenode) AS heap_blks_written, pg_stat_get_blocks_hit(C.oid) AS heap_blks_hit, I.idx_blks_read AS idx_blks_read, I.idx_blks_hit AS idx_blks_hit, @@ -758,7 +759,7 @@ CREATE VIEW pg_statio_all_tables AS pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit, X.idx_blks_read AS tidx_blks_read, X.idx_blks_hit AS tidx_blks_hit - FROM pg_class C LEFT JOIN + FROM pg_database d, pg_class C LEFT JOIN pg_class T ON C.reltoastrelid = T.oid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) LEFT JOIN LATERAL ( @@ -775,7 +776,7 @@ CREATE VIEW pg_statio_all_tables AS sum(pg_stat_get_blocks_hit(indexrelid))::bigint AS idx_blks_hit FROM pg_index WHERE indrelid = T.oid ) X ON true - WHERE C.relkind IN ('r', 't', 'm'); + WHERE C.relkind IN ('r', 't', 'm') AND d.datname = current_database(); CREATE VIEW pg_statio_sys_tables AS SELECT * FROM pg_statio_all_tables diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c index 3c68a9904d..0ff2812218 100644 --- a/src/backend/postmaster/checkpointer.c +++ b/src/backend/postmaster/checkpointer.c @@ -519,6 +519,11 @@ CheckpointerMain(char *startup_data, size_t startup_data_len) /* Report pending statistics to the cumulative stats system */ pgstat_report_checkpointer(); pgstat_report_wal(true); + /* + * No need to check for transaction state in checkpointer before + * calling pgstat_report_stat(). + */ + pgstat_report_stat(true); /* * If any checkpoint flags have been set, redo the loop to handle the diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 49637284f9..06d89ba26b 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -1121,9 +1121,9 @@ PinBufferForBlock(Relation rel, * WaitReadBuffers() (so, not for hits, and not for buffers that are * zeroed instead), the per-relation stats always count them. */ - pgstat_count_buffer_read(rel); + pgstat_report_relfilenode_buffer_read(rel); if (*foundPtr) - pgstat_count_buffer_hit(rel); + pgstat_report_relfilenode_buffer_hit(rel); } if (*foundPtr) { @@ -3838,6 +3838,8 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln, IOObject io_object, pgBufferUsage.shared_blks_written++; + pgstat_report_relfilenode_blks_written(reln->smgr_rlocator.locator); + /* * Mark the buffer as clean (unless BM_JUST_DIRTIED has become set) and * end the BM_IO_IN_PROGRESS state. diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c index bf0f3ca76d..3576749d2d 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -1447,12 +1447,16 @@ DropRelationFiles(RelFileLocator *delrels, int ndelrels, bool isRedo) { SMgrRelation *srels; int i; + int not_freed_count = 0; srels = palloc(sizeof(SMgrRelation) * ndelrels); for (i = 0; i < ndelrels; i++) { SMgrRelation srel = smgropen(delrels[i], INVALID_PROC_NUMBER); + if (!pgstat_drop_entry(PGSTAT_KIND_RELFILENODE, delrels[i].dbOid, delrels[i].spcOid, delrels[i].relNumber)) + not_freed_count++; + if (isRedo) { ForkNumber fork; @@ -1463,6 +1467,9 @@ DropRelationFiles(RelFileLocator *delrels, int ndelrels, bool isRedo) srels[i] = srel; } + if (not_freed_count > 0) + pgstat_request_entry_refs_gc(); + smgrdounlinkall(srels, ndelrels, isRedo); for (i = 0; i < ndelrels; i++) diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index dcc2ad8d95..e3b6f45828 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -288,6 +288,19 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = { .delete_pending_cb = pgstat_relation_delete_pending_cb, }, + [PGSTAT_KIND_RELFILENODE] = { + .name = "relfilenode", + + .fixed_amount = false, + + .shared_size = sizeof(PgStatShared_RelFileNode), + .shared_data_off = offsetof(PgStatShared_RelFileNode, stats), + .shared_data_len = sizeof(((PgStatShared_RelFileNode *) 0)->stats), + .pending_size = sizeof(PgStat_StatRelFileNodeEntry), + + .flush_pending_cb = pgstat_relfilenode_flush_cb, + }, + [PGSTAT_KIND_FUNCTION] = { .name = "function", @@ -651,7 +664,7 @@ pgstat_report_stat(bool force) partial_flush = false; - /* flush database / relation / function / ... stats */ + /* flush database / relation / function / relfilenode / ... stats */ partial_flush |= pgstat_flush_pending_entries(nowait); /* flush IO stats */ @@ -731,7 +744,7 @@ pgstat_reset_counters(void) * GRANT system. */ void -pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); TimestampTz ts = GetCurrentTimestamp(); @@ -740,7 +753,7 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid) Assert(!pgstat_get_kind_info(kind)->fixed_amount); /* reset the "single counter" */ - pgstat_reset_entry(kind, dboid, objoid, ts); + pgstat_reset_entry(kind, dboid, objoid, relfile, ts); if (!kind_info->accessed_across_databases) pgstat_reset_database_timestamp(dboid, ts); @@ -809,7 +822,7 @@ pgstat_clear_snapshot(void) } void * -pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { PgStat_HashKey key; PgStat_EntryRef *entry_ref; @@ -825,6 +838,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid) key.kind = kind; key.dboid = dboid; key.objoid = objoid; + key.relfile = relfile; /* if we need to build a full snapshot, do so */ if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT) @@ -850,7 +864,7 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid) pgStatLocal.snapshot.mode = pgstat_fetch_consistency; - entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL); if (entry_ref == NULL || entry_ref->shared_entry->dropped) { @@ -919,13 +933,13 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot) } bool -pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { /* fixed-numbered stats always exist */ if (pgstat_get_kind_info(kind)->fixed_amount) return true; - return pgstat_get_entry_ref(kind, dboid, objoid, false, NULL) != NULL; + return pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL) != NULL; } /* @@ -1102,7 +1116,8 @@ pgstat_build_snapshot_fixed(PgStat_Kind kind) * created, false otherwise. */ PgStat_EntryRef * -pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created_entry) +pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, + RelFileNumber relfile, bool *created_entry) { PgStat_EntryRef *entry_ref; @@ -1117,7 +1132,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created ALLOCSET_SMALL_SIZES); } - entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, true, created_entry); if (entry_ref->pending == NULL) @@ -1140,11 +1155,11 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created * that it shouldn't be needed. */ PgStat_EntryRef * -pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { PgStat_EntryRef *entry_ref; - entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL); if (entry_ref == NULL || entry_ref->pending == NULL) return NULL; @@ -1173,7 +1188,7 @@ pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref) } /* - * Flush out pending stats for database objects (databases, relations, + * Flush out pending stats for database objects (databases, relations, relfilenodes, * functions). */ static bool diff --git a/src/backend/utils/activity/pgstat_database.c b/src/backend/utils/activity/pgstat_database.c index 29bc090974..cf77f2dbdb 100644 --- a/src/backend/utils/activity/pgstat_database.c +++ b/src/backend/utils/activity/pgstat_database.c @@ -43,7 +43,7 @@ static PgStat_Counter pgLastSessionReportTime = 0; void pgstat_drop_database(Oid databaseid) { - pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid); + pgstat_drop_transactional(PGSTAT_KIND_DATABASE, databaseid, InvalidOid, InvalidOid); } /* @@ -66,7 +66,7 @@ pgstat_report_autovac(Oid dboid) * operation so it doesn't matter if we get blocked here a little. */ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, - dboid, InvalidOid, false); + dboid, InvalidOid, InvalidOid, false); dbentry = (PgStatShared_Database *) entry_ref->shared_stats; dbentry->stats.last_autovac_time = GetCurrentTimestamp(); @@ -150,7 +150,7 @@ pgstat_report_checksum_failures_in_db(Oid dboid, int failurecount) * common enough for that to be a problem. */ entry_ref = - pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, false); + pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, dboid, InvalidOid, InvalidOid, false); sharedent = (PgStatShared_Database *) entry_ref->shared_stats; sharedent->stats.checksum_failures += failurecount; @@ -242,7 +242,7 @@ PgStat_StatDBEntry * pgstat_fetch_stat_dbentry(Oid dboid) { return (PgStat_StatDBEntry *) - pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid); + pgstat_fetch_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, InvalidOid); } void @@ -341,7 +341,7 @@ pgstat_prep_database_pending(Oid dboid) Assert(!OidIsValid(dboid) || OidIsValid(MyDatabaseId)); entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_DATABASE, dboid, InvalidOid, - NULL); + InvalidOid, NULL); return entry_ref->pending; } @@ -357,7 +357,7 @@ pgstat_reset_database_timestamp(Oid dboid, TimestampTz ts) PgStatShared_Database *dbentry; dbref = pgstat_get_entry_ref_locked(PGSTAT_KIND_DATABASE, MyDatabaseId, InvalidOid, - false); + InvalidOid, false); dbentry = (PgStatShared_Database *) dbref->shared_stats; dbentry->stats.stat_reset_timestamp = ts; diff --git a/src/backend/utils/activity/pgstat_function.c b/src/backend/utils/activity/pgstat_function.c index d26da551a4..440e44e300 100644 --- a/src/backend/utils/activity/pgstat_function.c +++ b/src/backend/utils/activity/pgstat_function.c @@ -46,7 +46,8 @@ pgstat_create_function(Oid proid) { pgstat_create_transactional(PGSTAT_KIND_FUNCTION, MyDatabaseId, - proid); + proid, + InvalidOid); } /* @@ -61,7 +62,8 @@ pgstat_drop_function(Oid proid) { pgstat_drop_transactional(PGSTAT_KIND_FUNCTION, MyDatabaseId, - proid); + proid, + InvalidOid); } /* @@ -86,6 +88,7 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo, entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, fcinfo->flinfo->fn_oid, + InvalidOid, &created_entry); /* @@ -113,7 +116,7 @@ pgstat_init_function_usage(FunctionCallInfo fcinfo, if (!SearchSysCacheExists1(PROCOID, ObjectIdGetDatum(fcinfo->flinfo->fn_oid))) { pgstat_drop_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, - fcinfo->flinfo->fn_oid); + fcinfo->flinfo->fn_oid, InvalidOid); ereport(ERROR, errcode(ERRCODE_UNDEFINED_FUNCTION), errmsg("function call to dropped function")); } @@ -224,7 +227,7 @@ find_funcstat_entry(Oid func_id) { PgStat_EntryRef *entry_ref; - entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id, InvalidOid); if (entry_ref) return entry_ref->pending; @@ -239,5 +242,5 @@ PgStat_StatFuncEntry * pgstat_fetch_stat_funcentry(Oid func_id) { return (PgStat_StatFuncEntry *) - pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id); + pgstat_fetch_entry(PGSTAT_KIND_FUNCTION, MyDatabaseId, func_id, InvalidOid); } diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index 8a3f7d434c..136dd6c85b 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -44,6 +44,7 @@ typedef struct TwoPhasePgStatRecord static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared); +PgStat_StatRelFileNodeEntry *pgstat_prep_relfilenode_pending(RelFileLocator locator); static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level); static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info); static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop); @@ -69,6 +70,7 @@ pgstat_copy_relation_stats(Relation dst, Relation src) dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId, RelationGetRelid(dst), + InvalidOid, false); dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats; @@ -170,7 +172,7 @@ pgstat_create_relation(Relation rel) { pgstat_create_transactional(PGSTAT_KIND_RELATION, rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId, - RelationGetRelid(rel)); + RelationGetRelid(rel), InvalidOid); } /* @@ -184,7 +186,7 @@ pgstat_drop_relation(Relation rel) pgstat_drop_transactional(PGSTAT_KIND_RELATION, rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId, - RelationGetRelid(rel)); + RelationGetRelid(rel), InvalidOid); if (!pgstat_should_count_relation(rel)) return; @@ -225,7 +227,7 @@ pgstat_report_vacuum(Oid tableoid, bool shared, /* block acquiring lock for the same reason as pgstat_report_autovac() */ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, - dboid, tableoid, false); + dboid, tableoid, InvalidOid, false); shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; tabentry = &shtabentry->stats; @@ -318,6 +320,7 @@ pgstat_report_analyze(Relation rel, /* block acquiring lock for the same reason as pgstat_report_autovac() */ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid, RelationGetRelid(rel), + InvalidOid, false); /* can't get dropped while accessed */ Assert(entry_ref != NULL && entry_ref->shared_stats != NULL); @@ -458,6 +461,19 @@ pgstat_fetch_stat_tabentry(Oid relid) return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid); } +/* + * Support function for the SQL-callable pgstat* functions. Returns + * the collected statistics for one relfilenode or NULL. NULL doesn't mean + * that the relfilenode doesn't exist, just that there are no statistics, so the + * caller is better off to report ZERO instead. + */ +PgStat_StatRelFileNodeEntry * +pgstat_fetch_stat_relfilenodeentry(Oid dboid, Oid spcOid, RelFileNumber relfile) +{ + return (PgStat_StatRelFileNodeEntry *) + pgstat_fetch_entry(PGSTAT_KIND_RELFILENODE, dboid, spcOid, relfile); +} + /* * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify * whether the to-be-accessed table is a shared relation or not. @@ -468,7 +484,7 @@ pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid) Oid dboid = (shared ? InvalidOid : MyDatabaseId); return (PgStat_StatTabEntry *) - pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid); + pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid, InvalidOid); } /* @@ -491,10 +507,10 @@ find_tabstat_entry(Oid rel_id) PgStat_TableStatus *tabentry = NULL; PgStat_TableStatus *tablestatus = NULL; - entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id); + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id, InvalidOid); if (!entry_ref) { - entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id); + entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id, InvalidOid); if (!entry_ref) return tablestatus; } @@ -881,6 +897,38 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) return true; } +/* + * Flush out pending stats for the relfilenode entry + * + * If nowait is true, this function returns false if lock could not + * immediately acquired, otherwise true is returned. + */ +bool +pgstat_relfilenode_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) +{ + PgStatShared_RelFileNode *sharedent; + PgStat_StatRelFileNodeEntry *pendingent; + + pendingent = (PgStat_StatRelFileNodeEntry *) entry_ref->pending; + sharedent = (PgStatShared_RelFileNode *) entry_ref->shared_stats; + + if (!pgstat_lock_entry(entry_ref, nowait)) + return false; + +#define PGSTAT_ACCUM_RELFILENODECOUNT(item) \ + (sharedent)->stats.item += (pendingent)->item + + PGSTAT_ACCUM_RELFILENODECOUNT(blocks_fetched); + PGSTAT_ACCUM_RELFILENODECOUNT(blocks_hit); + PGSTAT_ACCUM_RELFILENODECOUNT(blocks_written); + + pgstat_unlock_entry(entry_ref); + + memset(pendingent, 0, sizeof(*pendingent)); + + return true; +} + void pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref) { @@ -902,7 +950,7 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared) entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION, isshared ? InvalidOid : MyDatabaseId, - rel_id, NULL); + rel_id, InvalidOid, NULL); pending = entry_ref->pending; pending->id = rel_id; pending->shared = isshared; @@ -910,6 +958,56 @@ pgstat_prep_relation_pending(Oid rel_id, bool isshared) return pending; } +PgStat_StatRelFileNodeEntry * +pgstat_prep_relfilenode_pending(RelFileLocator locator) +{ + PgStat_EntryRef *entry_ref; + + entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELFILENODE, locator.dbOid, + locator.spcOid, locator.relNumber, NULL); + + return entry_ref->pending; +} + +void +pgstat_report_relfilenode_blks_written(RelFileLocator locator) +{ + PgStat_StatRelFileNodeEntry *relfileentry = NULL; + + relfileentry = pgstat_prep_relfilenode_pending(locator); + + if (relfileentry) + relfileentry->blocks_written++; +} + +void +pgstat_report_relfilenode_buffer_read(Relation reln) +{ + PgStat_StatRelFileNodeEntry *relfileentry = NULL; + + /* For relation stats to survive after a rewrite */ + pgstat_count_buffer_read(reln); + + relfileentry = pgstat_prep_relfilenode_pending(reln->rd_locator); + + if (relfileentry) + relfileentry->blocks_fetched++; +} + +void +pgstat_report_relfilenode_buffer_hit(Relation reln) +{ + PgStat_StatRelFileNodeEntry *relfileentry = NULL; + + /* For relation stats to survive after a rewrite */ + pgstat_count_buffer_hit(reln); + + relfileentry = pgstat_prep_relfilenode_pending(reln->rd_locator); + + if (relfileentry) + relfileentry->blocks_hit++; +} + /* * add a new (sub)transaction state record */ diff --git a/src/backend/utils/activity/pgstat_replslot.c b/src/backend/utils/activity/pgstat_replslot.c index 889e86ac5a..96c6621477 100644 --- a/src/backend/utils/activity/pgstat_replslot.c +++ b/src/backend/utils/activity/pgstat_replslot.c @@ -62,7 +62,7 @@ pgstat_reset_replslot(const char *name) */ if (SlotIsLogical(slot)) pgstat_reset(PGSTAT_KIND_REPLSLOT, InvalidOid, - ReplicationSlotIndex(slot)); + ReplicationSlotIndex(slot), InvalidOid); LWLockRelease(ReplicationSlotControlLock); } @@ -82,7 +82,7 @@ pgstat_report_replslot(ReplicationSlot *slot, const PgStat_StatReplSlotEntry *re PgStat_StatReplSlotEntry *statent; entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid, - ReplicationSlotIndex(slot), false); + ReplicationSlotIndex(slot), InvalidOid, false); shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats; statent = &shstatent->stats; @@ -116,7 +116,7 @@ pgstat_create_replslot(ReplicationSlot *slot) Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE)); entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_REPLSLOT, InvalidOid, - ReplicationSlotIndex(slot), false); + ReplicationSlotIndex(slot), InvalidOid, false); shstatent = (PgStatShared_ReplSlot *) entry_ref->shared_stats; /* @@ -146,7 +146,7 @@ void pgstat_acquire_replslot(ReplicationSlot *slot) { pgstat_get_entry_ref(PGSTAT_KIND_REPLSLOT, InvalidOid, - ReplicationSlotIndex(slot), true, NULL); + ReplicationSlotIndex(slot), InvalidOid, true, NULL); } /* @@ -158,7 +158,7 @@ pgstat_drop_replslot(ReplicationSlot *slot) Assert(LWLockHeldByMeInMode(ReplicationSlotAllocationLock, LW_EXCLUSIVE)); pgstat_drop_entry(PGSTAT_KIND_REPLSLOT, InvalidOid, - ReplicationSlotIndex(slot)); + ReplicationSlotIndex(slot), InvalidOid); } /* @@ -177,7 +177,7 @@ pgstat_fetch_replslot(NameData slotname) if (idx != -1) slotentry = (PgStat_StatReplSlotEntry *) pgstat_fetch_entry(PGSTAT_KIND_REPLSLOT, - InvalidOid, idx); + InvalidOid, idx, InvalidOid); LWLockRelease(ReplicationSlotControlLock); @@ -209,6 +209,7 @@ pgstat_replslot_from_serialized_name_cb(const NameData *name, PgStat_HashKey *ke key->kind = PGSTAT_KIND_REPLSLOT; key->dboid = InvalidOid; key->objoid = idx; + key->relfile = InvalidOid; return true; } diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c index 91591da395..d74b07e414 100644 --- a/src/backend/utils/activity/pgstat_shmem.c +++ b/src/backend/utils/activity/pgstat_shmem.c @@ -395,10 +395,10 @@ pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p) * if the entry is newly created, false otherwise. */ PgStat_EntryRef * -pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, bool create, - bool *created_entry) +pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, + bool create, bool *created_entry) { - PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid}; + PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid,.relfile = relfile}; PgStatShared_HashEntry *shhashent; PgStatShared_Common *shheader = NULL; PgStat_EntryRef *entry_ref; @@ -611,12 +611,12 @@ pgstat_unlock_entry(PgStat_EntryRef *entry_ref) */ PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid, - bool nowait) + RelFileNumber relfile, bool nowait) { PgStat_EntryRef *entry_ref; /* find shared table stats entry corresponding to the local entry */ - entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, true, NULL); + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, true, NULL); /* lock the shared entry to protect the content, skip if failed */ if (!pgstat_lock_entry(entry_ref, nowait)) @@ -856,9 +856,9 @@ pgstat_drop_database_and_contents(Oid dboid) } bool -pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { - PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid}; + PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid,.relfile = relfile}; PgStatShared_HashEntry *shent; bool freed = true; @@ -931,13 +931,12 @@ shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header, * Reset one variable-numbered stats entry. */ void -pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts) +pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, TimestampTz ts) { PgStat_EntryRef *entry_ref; Assert(!pgstat_get_kind_info(kind)->fixed_amount); - - entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL); + entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL); if (!entry_ref || entry_ref->shared_entry->dropped) return; diff --git a/src/backend/utils/activity/pgstat_subscription.c b/src/backend/utils/activity/pgstat_subscription.c index d9af8de658..9b9ab2861b 100644 --- a/src/backend/utils/activity/pgstat_subscription.c +++ b/src/backend/utils/activity/pgstat_subscription.c @@ -30,7 +30,7 @@ pgstat_report_subscription_error(Oid subid, bool is_apply_error) PgStat_BackendSubEntry *pending; entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_SUBSCRIPTION, - InvalidOid, subid, NULL); + InvalidOid, subid, InvalidOid, NULL); pending = entry_ref->pending; if (is_apply_error) @@ -47,12 +47,12 @@ pgstat_create_subscription(Oid subid) { /* Ensures that stats are dropped if transaction rolls back */ pgstat_create_transactional(PGSTAT_KIND_SUBSCRIPTION, - InvalidOid, subid); + InvalidOid, subid, InvalidOid); /* Create and initialize the subscription stats entry */ - pgstat_get_entry_ref(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, + pgstat_get_entry_ref(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid, true, NULL); - pgstat_reset_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, 0); + pgstat_reset_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid, 0); } /* @@ -64,7 +64,7 @@ void pgstat_drop_subscription(Oid subid) { pgstat_drop_transactional(PGSTAT_KIND_SUBSCRIPTION, - InvalidOid, subid); + InvalidOid, subid, InvalidOid); } /* @@ -75,7 +75,7 @@ PgStat_StatSubEntry * pgstat_fetch_stat_subscription(Oid subid) { return (PgStat_StatSubEntry *) - pgstat_fetch_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid); + pgstat_fetch_entry(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid); } /* diff --git a/src/backend/utils/activity/pgstat_xact.c b/src/backend/utils/activity/pgstat_xact.c index 1877d22f14..b25df5112b 100644 --- a/src/backend/utils/activity/pgstat_xact.c +++ b/src/backend/utils/activity/pgstat_xact.c @@ -30,7 +30,7 @@ static void AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool static void AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth); -static PgStat_SubXactStatus *pgStatXactStack = NULL; +PgStat_SubXactStatus *pgStatXactStack = NULL; /* @@ -84,7 +84,7 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit) * Transaction that dropped an object committed. Drop the stats * too. */ - if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile)) not_freed_count++; } else if (!isCommit && pending->is_create) @@ -93,7 +93,7 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit) * Transaction that created an object aborted. Drop the stats * associated with the object. */ - if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile)) not_freed_count++; } @@ -105,6 +105,33 @@ AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit) pgstat_request_entry_refs_gc(); } +/* + * Remove a relfilenode stat from the list of stats to be dropped. + */ +void +PgStat_RemoveRelFileNodeFromDroppedStats(PgStat_SubXactStatus *xact_state, RelFileLocator rlocator) +{ + dlist_mutable_iter iter; + + if (dclist_count(&xact_state->pending_drops) == 0) + return; + + dclist_foreach_modify(iter, &xact_state->pending_drops) + { + PgStat_PendingDroppedStatsItem *pending = + dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); + xl_xact_stats_item *it = &pending->item; + + if (it->kind == PGSTAT_KIND_RELFILENODE && it->dboid == rlocator.dbOid + && it->objoid == rlocator.spcOid && it->relfile == rlocator.relNumber) + { + dclist_delete_from(&xact_state->pending_drops, &pending->node); + pfree(pending); + return; + } + } +} + /* * Called from access/transam/xact.c at subtransaction commit/abort. */ @@ -158,7 +185,7 @@ AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, * Subtransaction creating a new stats object aborted. Drop the * stats object. */ - if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile)) not_freed_count++; pfree(pending); } @@ -320,7 +347,11 @@ pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, { xl_xact_stats_item *it = &items[i]; - if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid)) + /* leave it to pgstat_drop_transactional() in RelationDropStorage() */ + if (it->kind == PGSTAT_KIND_RELFILENODE) + continue; + + if (!pgstat_drop_entry(it->kind, it->dboid, it->objoid, it->relfile)) not_freed_count++; } @@ -329,7 +360,7 @@ pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, } static void -create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool is_create) +create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, bool is_create) { int nest_level = GetCurrentTransactionNestLevel(); PgStat_SubXactStatus *xact_state; @@ -342,6 +373,7 @@ create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool drop->item.kind = kind; drop->item.dboid = dboid; drop->item.objoid = objoid; + drop->item.relfile = relfile; dclist_push_tail(&xact_state->pending_drops, &drop->node); } @@ -354,18 +386,18 @@ create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, Oid objoid, bool * dropped. */ void -pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { - if (pgstat_get_entry_ref(kind, dboid, objoid, false, NULL)) + if (pgstat_get_entry_ref(kind, dboid, objoid, relfile, false, NULL)) { ereport(WARNING, - errmsg("resetting existing statistics for kind %s, db=%u, oid=%u", - (pgstat_get_kind_info(kind))->name, dboid, objoid)); + errmsg("resetting existing statistics for kind %s, db=%u, oid=%u, relfile=%u", + (pgstat_get_kind_info(kind))->name, dboid, objoid, relfile)); - pgstat_reset(kind, dboid, objoid); + pgstat_reset(kind, dboid, objoid, relfile); } - create_drop_transactional_internal(kind, dboid, objoid, /* create */ true); + create_drop_transactional_internal(kind, dboid, objoid, relfile, /* create */ true); } /* @@ -376,7 +408,7 @@ pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid) * alive. */ void -pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid) +pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile) { - create_drop_transactional_internal(kind, dboid, objoid, /* create */ false); + create_drop_transactional_internal(kind, dboid, objoid, relfile, /* create */ false); } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 3876339ee1..e266d96f5e 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -106,6 +106,30 @@ PG_STAT_GET_RELENTRY_INT64(tuples_updated) /* pg_stat_get_vacuum_count */ PG_STAT_GET_RELENTRY_INT64(vacuum_count) +#define PG_STAT_GET_RELFILEENTRY_INT64(stat) \ +Datum \ +CppConcat(pg_stat_get_relfilenode_,stat)(PG_FUNCTION_ARGS) \ +{ \ + Oid dboid = PG_GETARG_OID(0); \ + Oid spcOid = PG_GETARG_OID(1); \ + RelFileNumber relfile = PG_GETARG_OID(2); \ + int64 result; \ + PgStat_StatRelFileNodeEntry *relfileentry; \ + \ + if ((relfileentry = pgstat_fetch_stat_relfilenodeentry(dboid, spcOid, relfile)) == NULL) \ + result = 0; \ + else \ + result = (int64) (relfileentry->stat); \ + \ + PG_RETURN_INT64(result); \ +} + +/* pg_stat_get_relfilenode_blocks_written */ +PG_STAT_GET_RELFILEENTRY_INT64(blocks_written) + +/* pg_stat_get_blocks_written */ +PG_STAT_GET_RELENTRY_INT64(blocks_written) + #define PG_STAT_GET_RELENTRY_TIMESTAMPTZ(stat) \ Datum \ CppConcat(pg_stat_get_,stat)(PG_FUNCTION_ARGS) \ @@ -1752,7 +1776,7 @@ pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS) Oid taboid = PG_GETARG_OID(0); Oid dboid = (IsSharedRelation(taboid) ? InvalidOid : MyDatabaseId); - pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid); + pgstat_reset(PGSTAT_KIND_RELATION, dboid, taboid, InvalidOid); PG_RETURN_VOID(); } @@ -1762,7 +1786,7 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS) { Oid funcoid = PG_GETARG_OID(0); - pgstat_reset(PGSTAT_KIND_FUNCTION, MyDatabaseId, funcoid); + pgstat_reset(PGSTAT_KIND_FUNCTION, MyDatabaseId, funcoid, InvalidOid); PG_RETURN_VOID(); } @@ -1820,7 +1844,7 @@ pg_stat_reset_subscription_stats(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid subscription OID %u", subid))); - pgstat_reset(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid); + pgstat_reset(PGSTAT_KIND_SUBSCRIPTION, InvalidOid, subid, InvalidOid); } PG_RETURN_VOID(); @@ -2028,7 +2052,9 @@ pg_stat_have_stats(PG_FUNCTION_ARGS) char *stats_type = text_to_cstring(PG_GETARG_TEXT_P(0)); Oid dboid = PG_GETARG_OID(1); Oid objoid = PG_GETARG_OID(2); + Oid relfile = PG_GETARG_OID(3); + PgStat_Kind kind = pgstat_get_kind_from_str(stats_type); - PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid)); + PG_RETURN_BOOL(pgstat_have_entry(kind, dboid, objoid, relfile)); } diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index 8e583b45cd..792cd2237e 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -21,7 +21,9 @@ #include "access/sdir.h" #include "access/xact.h" #include "executor/tuptable.h" +#include "pgstat.h" #include "storage/read_stream.h" +#include "utils/pgstat_internal.h" #include "utils/rel.h" #include "utils/snapshot.h" @@ -1634,6 +1636,23 @@ table_relation_set_new_filelocator(Relation rel, TransactionId *freezeXid, MultiXactId *minmulti) { + PgStat_StatRelFileNodeEntry *relfileentry; + PgStat_StatTabEntry *tabentry = NULL; + PgStat_EntryRef *entry_ref = NULL; + PgStatShared_Relation *shtabentry; + + entry_ref = pgstat_get_entry_ref(PGSTAT_KIND_RELATION, MyDatabaseId, rel->rd_id, InvalidOid, false, NULL); + if (entry_ref) + { + shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats; + tabentry = &shtabentry->stats; + } + + relfileentry = pgstat_fetch_stat_relfilenodeentry(rel->rd_locator.dbOid, rel->rd_locator.spcOid, rel->rd_locator.relNumber); + + if (tabentry && relfileentry) + tabentry->blocks_written += relfileentry->blocks_written; + rel->rd_tableam->relation_set_new_filelocator(rel, newrlocator, persistence, freezeXid, minmulti); diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 6d4439f052..3b9ed65ff6 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -284,6 +284,7 @@ typedef struct xl_xact_stats_item int kind; Oid dboid; Oid objoid; + RelFileNumber relfile; } xl_xact_stats_item; typedef struct xl_xact_stats_items diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 6a5476d3c4..912471a1ac 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5374,6 +5374,14 @@ proname => 'pg_stat_get_tuples_updated', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', prosrc => 'pg_stat_get_tuples_updated' }, +{ oid => '9280', descr => 'statistics: number of blocks written', + proname => 'pg_stat_get_relfilenode_blocks_written', provolatile => 's', + proparallel => 'r', + proargtypes => 'oid oid oid', + prorettype => 'int8', + proallargtypes => '{oid,oid,oid,int8}', + proargmodes => '{i,i,i,o}', + prosrc => 'pg_stat_get_relfilenode_blocks_written' }, { oid => '1933', descr => 'statistics: number of tuples deleted', proname => 'pg_stat_get_tuples_deleted', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', @@ -5413,6 +5421,10 @@ proname => 'pg_stat_get_blocks_hit', provolatile => 's', proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', prosrc => 'pg_stat_get_blocks_hit' }, +{ oid => '8438', descr => 'statistics: number of blocks written', + proname => 'pg_stat_get_blocks_written', provolatile => 's', proparallel => 'r', + prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_blocks_written' }, { oid => '2781', descr => 'statistics: last manual vacuum time for a table', proname => 'pg_stat_get_last_vacuum_time', provolatile => 's', proparallel => 'r', prorettype => 'timestamptz', proargtypes => 'oid', @@ -5499,7 +5511,7 @@ { oid => '6230', descr => 'statistics: check if a stats object exists', proname => 'pg_stat_have_stats', provolatile => 'v', proparallel => 'r', - prorettype => 'bool', proargtypes => 'text oid oid', + prorettype => 'bool', proargtypes => 'text oid oid oid', prosrc => 'pg_stat_have_stats' }, { oid => '6231', descr => 'statistics: information about subscription stats', diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 2136239710..9631689430 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -14,6 +14,7 @@ #include "datatype/timestamp.h" #include "portability/instr_time.h" #include "postmaster/pgarch.h" /* for MAX_XFN_CHARS */ +#include "storage/relfilelocator.h" #include "utils/backend_progress.h" /* for backward compatibility */ #include "utils/backend_status.h" /* for backward compatibility */ #include "utils/relcache.h" @@ -40,6 +41,7 @@ typedef enum PgStat_Kind /* stats for variable-numbered objects */ PGSTAT_KIND_DATABASE, /* database-wide statistics */ PGSTAT_KIND_RELATION, /* per-table statistics */ + PGSTAT_KIND_RELFILENODE, /* per-relfilenode statistics */ PGSTAT_KIND_FUNCTION, /* per-function statistics */ PGSTAT_KIND_REPLSLOT, /* per-slot statistics */ PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */ @@ -417,6 +419,7 @@ typedef struct PgStat_StatTabEntry PgStat_Counter blocks_fetched; PgStat_Counter blocks_hit; + PgStat_Counter blocks_written; TimestampTz last_vacuum_time; /* user initiated vacuum */ PgStat_Counter vacuum_count; @@ -428,6 +431,13 @@ typedef struct PgStat_StatTabEntry PgStat_Counter autoanalyze_count; } PgStat_StatTabEntry; +typedef struct PgStat_StatRelFileNodeEntry +{ + PgStat_Counter blocks_fetched; + PgStat_Counter blocks_hit; + PgStat_Counter blocks_written; +} PgStat_StatRelFileNodeEntry; + typedef struct PgStat_WalStats { PgStat_Counter wal_records; @@ -478,7 +488,7 @@ extern long pgstat_report_stat(bool force); extern void pgstat_force_next_flush(void); extern void pgstat_reset_counters(void); -extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid); +extern void pgstat_reset(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); extern void pgstat_reset_of_kind(PgStat_Kind kind); /* stats accessors */ @@ -487,7 +497,7 @@ extern TimestampTz pgstat_get_stat_snapshot_timestamp(bool *have_snapshot); /* helpers */ extern PgStat_Kind pgstat_get_kind_from_str(char *kind_str); -extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid); +extern bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); /* @@ -596,6 +606,10 @@ extern void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter); +extern void pgstat_report_relfilenode_blks_written(RelFileLocator locator); +extern void pgstat_report_relfilenode_buffer_read(Relation reln); +extern void pgstat_report_relfilenode_buffer_hit(Relation reln); + /* * If stats are enabled, but pending data hasn't been prepared yet, call * pgstat_assoc_relation() to do so. See its comment for why this is done @@ -655,6 +669,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info, void *recdata, uint32 len); extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid); +extern PgStat_StatRelFileNodeEntry *pgstat_fetch_stat_relfilenodeentry(Oid dboid, Oid spcOid, RelFileNumber relfile); extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid); extern PgStat_TableStatus *find_tabstat_entry(Oid rel_id); diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h index dbbca31602..50d5f1a577 100644 --- a/src/include/utils/pgstat_internal.h +++ b/src/include/utils/pgstat_internal.h @@ -53,7 +53,8 @@ typedef struct PgStat_HashKey { PgStat_Kind kind; /* statistics entry kind */ Oid dboid; /* database ID. InvalidOid for shared objects. */ - Oid objoid; /* object ID, either table or function. */ + Oid objoid; /* object ID, either table or function or tablespace. */ + RelFileNumber relfile; /* relfilenumber for RelFileLocator. */ } PgStat_HashKey; /* @@ -376,6 +377,12 @@ typedef struct PgStatShared_Relation PgStat_StatTabEntry stats; } PgStatShared_Relation; +typedef struct PgStatShared_RelFileNode +{ + PgStatShared_Common header; + PgStat_StatRelFileNodeEntry stats; +} PgStatShared_RelFileNode; + typedef struct PgStatShared_Function { PgStatShared_Common header; @@ -498,6 +505,9 @@ static inline size_t pgstat_get_entry_len(PgStat_Kind kind); static inline void *pgstat_get_entry_data(PgStat_Kind kind, PgStatShared_Common *entry); +extern PgStat_SubXactStatus *pgStatXactStack; +extern void PgStat_RemoveRelFileNodeFromDroppedStats(PgStat_SubXactStatus *xact_state, RelFileLocator rlocator); + /* * Functions in pgstat.c */ @@ -511,10 +521,12 @@ extern void pgstat_assert_is_up(void); #endif extern void pgstat_delete_pending_entry(PgStat_EntryRef *entry_ref); -extern PgStat_EntryRef *pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, bool *created_entry); -extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid); +extern PgStat_EntryRef *pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, + Oid objoid, RelFileNumber relfile, + bool *created_entry); +extern PgStat_EntryRef *pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); -extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid); +extern void *pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); extern void pgstat_snapshot_fixed(PgStat_Kind kind); @@ -582,6 +594,7 @@ extern void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state); extern void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state); extern bool pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait); +extern bool pgstat_relfilenode_flush_cb(PgStat_EntryRef *entry_ref, bool nowait); extern void pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref); @@ -602,15 +615,16 @@ extern void pgstat_attach_shmem(void); extern void pgstat_detach_shmem(void); extern PgStat_EntryRef *pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, - bool create, bool *created_entry); + RelFileNumber relfile, bool create, + bool *created_entry); extern bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait); extern bool pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait); extern void pgstat_unlock_entry(PgStat_EntryRef *entry_ref); -extern bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid); +extern bool pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); extern void pgstat_drop_all_entries(void); extern PgStat_EntryRef *pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid, - bool nowait); -extern void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts); + RelFileNumber relfile, bool nowait); +extern void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile, TimestampTz ts); extern void pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts); extern void pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum), Datum match_data, @@ -655,8 +669,8 @@ extern void pgstat_subscription_reset_timestamp_cb(PgStatShared_Common *header, */ extern PgStat_SubXactStatus *pgstat_get_xact_stack_level(int nest_level); -extern void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid); -extern void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid); +extern void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); +extern void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid, RelFileNumber relfile); /* diff --git a/src/test/recovery/t/029_stats_restart.pl b/src/test/recovery/t/029_stats_restart.pl index 6a1615a1e8..ee5a404b45 100644 --- a/src/test/recovery/t/029_stats_restart.pl +++ b/src/test/recovery/t/029_stats_restart.pl @@ -40,10 +40,10 @@ trigger_funcrel_stat(); # verify stats objects exist my $sect = "initial"; -is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist"); +is(have_stats('function', $dboid, $funcoid, 0), 't', "$sect: function stats do exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 't', "$sect: relation stats do exist"); # regular shutdown @@ -64,10 +64,10 @@ copy($og_stats, $statsfile) or die "Copy failed: $!"; $node->start; $sect = "copy"; -is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist"); +is(have_stats('function', $dboid, $funcoid, 0), 't', "$sect: function stats do exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 't', "$sect: relation stats do exist"); $node->stop('immediate'); @@ -81,10 +81,10 @@ $node->start; # stats should have been discarded $sect = "post immediate"; -is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist"); +is(have_stats('function', $dboid, $funcoid, 0), 'f', "$sect: function stats do exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 'f', "$sect: relation stats do not exist"); # get rid of backup statsfile @@ -95,10 +95,10 @@ unlink $statsfile or die "cannot unlink $statsfile $!"; trigger_funcrel_stat(); $sect = "post immediate, new"; -is(have_stats('database', $dboid, 0), 't', "$sect: db stats do exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 't', "$sect: db stats do exist"); +is(have_stats('function', $dboid, $funcoid, 0), 't', "$sect: function stats do exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 't', "$sect: relation stats do exist"); # regular shutdown @@ -114,10 +114,10 @@ $node->start; # no stats present due to invalid stats file $sect = "invalid_overwrite"; -is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist"); +is(have_stats('function', $dboid, $funcoid, 0), 'f', "$sect: function stats do not exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 'f', "$sect: relation stats do not exist"); @@ -130,10 +130,10 @@ append_file($og_stats, "XYZ"); $node->start; $sect = "invalid_append"; -is(have_stats('database', $dboid, 0), 'f', "$sect: db stats do not exist"); -is(have_stats('function', $dboid, $funcoid), +is(have_stats('database', $dboid, 0, 0), 'f', "$sect: db stats do not exist"); +is(have_stats('function', $dboid, $funcoid, 0), 'f', "$sect: function stats do not exist"); -is(have_stats('relation', $dboid, $tableoid), +is(have_stats('relation', $dboid, $tableoid, 0), 'f', "$sect: relation stats do not exist"); @@ -292,10 +292,10 @@ sub trigger_funcrel_stat sub have_stats { - my ($kind, $dboid, $objoid) = @_; + my ($kind, $dboid, $objoid, $relfile) = @_; return $node->safe_psql($connect_db, - "SELECT pg_stat_have_stats('$kind', $dboid, $objoid)"); + "SELECT pg_stat_have_stats('$kind', $dboid, $objoid, $relfile)"); } sub overwrite_file diff --git a/src/test/recovery/t/030_stats_cleanup_replica.pl b/src/test/recovery/t/030_stats_cleanup_replica.pl index 74b516cc7c..317df24c4f 100644 --- a/src/test/recovery/t/030_stats_cleanup_replica.pl +++ b/src/test/recovery/t/030_stats_cleanup_replica.pl @@ -179,9 +179,9 @@ sub test_standby_func_tab_stats_status my %stats; $stats{rel} = $node_standby->safe_psql($connect_db, - "SELECT pg_stat_have_stats('relation', $dboid, $tableoid)"); + "SELECT pg_stat_have_stats('relation', $dboid, $tableoid, 0)"); $stats{func} = $node_standby->safe_psql($connect_db, - "SELECT pg_stat_have_stats('function', $dboid, $funcoid)"); + "SELECT pg_stat_have_stats('function', $dboid, $funcoid, 0)"); is_deeply(\%stats, \%expected, "$sect: standby stats as expected"); @@ -194,7 +194,7 @@ sub test_standby_db_stats_status my ($connect_db, $dboid, $present) = @_; is( $node_standby->safe_psql( - $connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0)"), + $connect_db, "SELECT pg_stat_have_stats('database', $dboid, 0, 0)"), $present, "$sect: standby db stats as expected"); } diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index ef658ad740..a2fa165c4c 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2335,6 +2335,11 @@ pg_statio_all_tables| SELECT c.oid AS relid, n.nspname AS schemaname, c.relname, (pg_stat_get_blocks_fetched(c.oid) - pg_stat_get_blocks_hit(c.oid)) AS heap_blks_read, + (pg_stat_get_blocks_written(c.oid) + pg_stat_get_relfilenode_blocks_written(d.oid, + CASE + WHEN (c.reltablespace <> (0)::oid) THEN c.reltablespace + ELSE d.dattablespace + END, c.relfilenode)) AS heap_blks_written, pg_stat_get_blocks_hit(c.oid) AS heap_blks_hit, i.idx_blks_read, i.idx_blks_hit, @@ -2342,7 +2347,8 @@ pg_statio_all_tables| SELECT c.oid AS relid, pg_stat_get_blocks_hit(t.oid) AS toast_blks_hit, x.idx_blks_read AS tidx_blks_read, x.idx_blks_hit AS tidx_blks_hit - FROM ((((pg_class c + FROM pg_database d, + ((((pg_class c LEFT JOIN pg_class t ON ((c.reltoastrelid = t.oid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) LEFT JOIN LATERAL ( SELECT (sum((pg_stat_get_blocks_fetched(pg_index.indexrelid) - pg_stat_get_blocks_hit(pg_index.indexrelid))))::bigint AS idx_blks_read, @@ -2353,7 +2359,7 @@ pg_statio_all_tables| SELECT c.oid AS relid, (sum(pg_stat_get_blocks_hit(pg_index.indexrelid)))::bigint AS idx_blks_hit FROM pg_index WHERE (pg_index.indrelid = t.oid)) x ON (true)) - WHERE (c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])); + WHERE ((c.relkind = ANY (ARRAY['r'::"char", 't'::"char", 'm'::"char"])) AND (d.datname = current_database())); pg_statio_sys_indexes| SELECT relid, indexrelid, schemaname, @@ -2374,6 +2380,7 @@ pg_statio_sys_tables| SELECT relid, schemaname, relname, heap_blks_read, + heap_blks_written, heap_blks_hit, idx_blks_read, idx_blks_hit, @@ -2403,6 +2410,7 @@ pg_statio_user_tables| SELECT relid, schemaname, relname, heap_blks_read, + heap_blks_written, heap_blks_hit, idx_blks_read, idx_blks_hit, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index 6e08898b18..eff0c9372c 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -1111,23 +1111,23 @@ ROLLBACK; -- pg_stat_have_stats behavior ---- -- fixed-numbered stats exist -SELECT pg_stat_have_stats('bgwriter', 0, 0); +SELECT pg_stat_have_stats('bgwriter', 0, 0, 0); pg_stat_have_stats -------------------- t (1 row) -- unknown stats kinds error out -SELECT pg_stat_have_stats('zaphod', 0, 0); +SELECT pg_stat_have_stats('zaphod', 0, 0, 0); ERROR: invalid statistics kind: "zaphod" -- db stats have objoid 0 -SELECT pg_stat_have_stats('database', :dboid, 1); +SELECT pg_stat_have_stats('database', :dboid, 1, 0); pg_stat_have_stats -------------------- f (1 row) -SELECT pg_stat_have_stats('database', :dboid, 0); +SELECT pg_stat_have_stats('database', :dboid, 0, 0); pg_stat_have_stats -------------------- t @@ -1144,21 +1144,21 @@ select a from stats_test_tab1 where a = 3; 3 (1 row) -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t (1 row) -- pg_stat_have_stats returns false for dropped index with stats -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t (1 row) DROP index stats_test_idx1; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- f @@ -1174,14 +1174,14 @@ select a from stats_test_tab1 where a = 3; 3 (1 row) -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t (1 row) ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- f @@ -1196,7 +1196,7 @@ select a from stats_test_tab1 where a = 3; 3 (1 row) -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t @@ -1204,7 +1204,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); REINDEX index CONCURRENTLY stats_test_idx1; -- false for previous oid -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- f @@ -1212,7 +1212,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); -- true for new oid SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t @@ -1220,7 +1220,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); -- pg_stat_have_stats returns true for a rolled back drop index with stats BEGIN; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t @@ -1228,7 +1228,7 @@ SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); DROP index stats_test_idx1; ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); pg_stat_have_stats -------------------- t @@ -1513,7 +1513,7 @@ SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_ext (1 row) -- Test IO stats reset -SELECT pg_stat_have_stats('io', 0, 0); +SELECT pg_stat_have_stats('io', 0, 0, 0); pg_stat_have_stats -------------------- t diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index d8ac0d06f4..5a40779989 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -539,12 +539,12 @@ ROLLBACK; -- pg_stat_have_stats behavior ---- -- fixed-numbered stats exist -SELECT pg_stat_have_stats('bgwriter', 0, 0); +SELECT pg_stat_have_stats('bgwriter', 0, 0, 0); -- unknown stats kinds error out -SELECT pg_stat_have_stats('zaphod', 0, 0); +SELECT pg_stat_have_stats('zaphod', 0, 0, 0); -- db stats have objoid 0 -SELECT pg_stat_have_stats('database', :dboid, 1); -SELECT pg_stat_have_stats('database', :dboid, 0); +SELECT pg_stat_have_stats('database', :dboid, 1, 0); +SELECT pg_stat_have_stats('database', :dboid, 0, 0); -- pg_stat_have_stats returns true for committed index creation CREATE table stats_test_tab1 as select generate_series(1,10) a; @@ -552,40 +552,40 @@ CREATE index stats_test_idx1 on stats_test_tab1(a); SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset SET enable_seqscan TO off; select a from stats_test_tab1 where a = 3; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- pg_stat_have_stats returns false for dropped index with stats -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); DROP index stats_test_idx1; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- pg_stat_have_stats returns false for rolled back index creation BEGIN; CREATE index stats_test_idx1 on stats_test_tab1(a); SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset select a from stats_test_tab1 where a = 3; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- pg_stat_have_stats returns true for reindex CONCURRENTLY CREATE index stats_test_idx1 on stats_test_tab1(a); SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset select a from stats_test_tab1 where a = 3; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); REINDEX index CONCURRENTLY stats_test_idx1; -- false for previous oid -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- true for new oid SELECT 'stats_test_idx1'::regclass::oid AS stats_test_idx1_oid \gset -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- pg_stat_have_stats returns true for a rolled back drop index with stats BEGIN; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); DROP index stats_test_idx1; ROLLBACK; -SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid); +SELECT pg_stat_have_stats('relation', :dboid, :stats_test_idx1_oid, 0); -- put enable_seqscan back to on SET enable_seqscan TO on; @@ -759,7 +759,7 @@ SELECT sum(extends) AS io_sum_bulkwrite_strategy_extends_after SELECT :io_sum_bulkwrite_strategy_extends_after > :io_sum_bulkwrite_strategy_extends_before; -- Test IO stats reset -SELECT pg_stat_have_stats('io', 0, 0); +SELECT pg_stat_have_stats('io', 0, 0, 0); SELECT sum(evictions) + sum(reuses) + sum(extends) + sum(fsyncs) + sum(reads) + sum(writes) + sum(writebacks) + sum(hits) AS io_stats_pre_reset FROM pg_stat_io \gset SELECT pg_stat_reset_shared('io'); diff --git a/src/test/subscription/t/026_stats.pl b/src/test/subscription/t/026_stats.pl index fb3e5629b3..1f4ae5efd5 100644 --- a/src/test/subscription/t/026_stats.pl +++ b/src/test/subscription/t/026_stats.pl @@ -263,7 +263,7 @@ $node_subscriber->safe_psql($db, qq(DROP SUBSCRIPTION $sub1_name)); # Subscription stats for sub1 should be gone is( $node_subscriber->safe_psql( - $db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub1_oid))), + $db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub1_oid, 0))), qq(f), qq(Subscription stats for subscription '$sub1_name' should be removed.)); @@ -282,7 +282,7 @@ DROP SUBSCRIPTION $sub2_name; # Subscription stats for sub2 should be gone is( $node_subscriber->safe_psql( - $db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub2_oid))), + $db, qq(SELECT pg_stat_have_stats('subscription', 0, $sub2_oid, 0))), qq(f), qq(Subscription stats for subscription '$sub2_name' should be removed.)); diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index d427a1c16a..d7385f9bfb 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2118,6 +2118,7 @@ PgStatShared_Function PgStatShared_HashEntry PgStatShared_IO PgStatShared_Relation +PgStatShared_RelFileNode PgStatShared_ReplSlot PgStatShared_SLRU PgStatShared_Subscription -- 2.34.1 --TqZvpUxcxjengx0M-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v7 2/2] fixups @ 2026-02-09 16:51 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-02-09 16:51 UTC (permalink / raw) --- src/backend/meson.build | 10 +++--- src/backend/utils/error/elog.c | 57 ++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/src/backend/meson.build b/src/backend/meson.build index 2e7f2be2c78..1ee2c079390 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -2,10 +2,6 @@ backend_build_deps = [backend_code] -if host_system == 'windows' and cc.get_id() == 'msvc' - backend_build_deps += cc.find_library('dbghelp') -endif - backend_sources = [] backend_link_with = [pgport_srv, common_srv] @@ -46,6 +42,12 @@ backend_link_args = [] backend_link_depends = [] +# On Windows also make the backend depend on dbghelp, for backtrace support +if host_system == 'windows' and cc.get_id() == 'msvc' + backend_build_deps += cc.find_library('dbghelp') +endif + + # On windows when compiling with msvc we need to make postgres export all its # symbols so that extension libraries can use them. For that we need to scan # the constituting objects and generate a file specifying all the functions as diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 60f95a58f7a..6b80e44fb5d 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -74,6 +74,7 @@ #include "common/ip.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" +#include "mb/pg_wchar.h" #include "miscadmin.h" #include "nodes/miscnodes.h" #include "pgstat.h" @@ -188,6 +189,7 @@ static void set_stack_entry_location(ErrorData *edata, const char *funcname); static bool matches_backtrace_functions(const char *funcname); static pg_noinline void set_backtrace(ErrorData *edata, int num_skip); +static void backtrace_cleanup(int code, Datum arg); static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *str); static void FreeErrorDataContents(ErrorData *edata); static int log_min_messages_cmp(const ListCell *a, const ListCell *b); @@ -1125,30 +1127,17 @@ errbacktrace(void) return 0; } -#ifdef _MSC_VER -/* - * Cleanup function for DbgHelp resources. - * Called via on_proc_exit() to release resources allocated by SymInitialize(). - */ -static void -backtrace_cleanup(int code, Datum arg) -{ - SymCleanup(backtrace_process); -} -#endif - /* * Compute backtrace data and add it to the supplied ErrorData. num_skip * specifies how many inner frames to skip. Use this to avoid showing the * internal backtrace support functions in the backtrace. This requires that * this and related functions are not inlined. * - * Platform-specific implementations: - * - Unix/Linux: Uses backtrace() and backtrace_symbols() + * The implementation is, unsurprisingly, platform-specific: + * - Linux, Unix: Uses backtrace() and backtrace_symbols() * - Windows: Uses CaptureStackBackTrace() with DbgHelp for symbol resolution * (requires PDB files; falls back to exported functions/raw addresses if * unavailable) - * - Other: Returns unsupported message */ static void set_backtrace(ErrorData *edata, int num_skip) @@ -1159,12 +1148,12 @@ set_backtrace(ErrorData *edata, int num_skip) #ifdef HAVE_BACKTRACE_SYMBOLS { - void *buf[100]; + void *frames[100]; int nframes; char **strfrms; - nframes = backtrace(buf, lengthof(buf)); - strfrms = backtrace_symbols(buf, nframes); + nframes = backtrace(frames, lengthof(frames)); + strfrms = backtrace_symbols(frames, nframes); if (strfrms != NULL) { for (int i = num_skip; i < nframes; i++) @@ -1177,7 +1166,7 @@ set_backtrace(ErrorData *edata, int num_skip) } #elif defined(_MSC_VER) { - void *buf[100]; + void *frames[100]; int nframes; char buffer[sizeof(SYMBOL_INFOW) + MAX_SYM_NAME * sizeof(wchar_t)]; PSYMBOL_INFOW psymbol; @@ -1198,18 +1187,19 @@ set_backtrace(ErrorData *edata, int num_skip) } else { - elog(WARNING, "could not initialize the symbol handler: error code %lu", - GetLastError()); + appendStringInfo(&errtrace, + "could not initialize symbol handler: error code %lu", + GetLastError()); edata->backtrace = errtrace.data; return; } } - nframes = CaptureStackBackTrace(num_skip, lengthof(buf), buf, NULL); + nframes = CaptureStackBackTrace(num_skip, lengthof(frames), frames, NULL); if (nframes == 0) { - appendStringInfoString(&errtrace, "\nNo stack frames captured"); + appendStringInfoString(&errtrace, "zero stack frames captured"); edata->backtrace = errtrace.data; return; } @@ -1220,7 +1210,7 @@ set_backtrace(ErrorData *edata, int num_skip) for (int i = 0; i < nframes; i++) { - DWORD64 address = (DWORD64) buf[i]; + DWORD64 address = (DWORD64) frames[i]; DWORD64 displacement = 0; BOOL sym_result; @@ -1284,8 +1274,10 @@ set_backtrace(ErrorData *edata, int num_skip) } else { - elog(WARNING, "symbol lookup failed: error code %lu", - GetLastError()); + appendStringInfo(&errtrace, + "\n[0x%llx] (symbol lookup failed: error code %lu)", + (unsigned long long) address, + GetLastError()); } } } @@ -1297,6 +1289,19 @@ set_backtrace(ErrorData *edata, int num_skip) edata->backtrace = errtrace.data; } +/* + * Cleanup function for DbgHelp resources. + * Called via on_proc_exit() to release resources allocated by SymInitialize(). + */ +pg_attribute_unused() +static void +backtrace_cleanup(int code, Datum arg) +{ +#ifdef _MSC_VER + SymCleanup(backtrace_process); +#endif +} + /* * errmsg_internal --- add a primary error message text to the current error * -- 2.47.3 --66lzhokyymna6c2w-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 152+ messages in thread From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw) --- src/backend/commands/repack.c | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 2879c8af574..fcc401ccdb9 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) /* * For USING INDEX, scan pg_index to find those with indisclustered. + * + * Note we don't obtain lock of any kind on the index, which means the + * index or its owning table could be gone or change at any point. We + * have to be extra careful when examining catalog state for them. */ catalog = table_open(IndexRelationId, AccessShareLock); ScanKeyInit(&entry, @@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) index = (Form_pg_index) GETSTRUCT(tuple); - classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid)); + classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid)); if (!HeapTupleIsValid(classtup)) continue; classForm = (Form_pg_class) GETSTRUCT(classtup); @@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt) if (classForm->relpersistence == RELPERSISTENCE_TEMP && !isTempOrTempToastNamespace(classForm->relnamespace)) { - ReleaseSysCache(classtup); + heap_freetuple(classtup); continue; } - ReleaseSysCache(classtup); + heap_freetuple(classtup); /* noisily skip rels which the user can't process */ if (!repack_is_permitted_for_relation(cmd, index->indrelid, @@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, if (get_rel_relkind(child_oid) != RELKIND_INDEX) continue; - table_oid = IndexGetRelation(child_oid, false); + table_oid = IndexGetRelation(child_oid, true); + if (!OidIsValid(table_oid)) + continue; index_oid = child_oid; } else @@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid, /* - * Return whether userid has privileges to REPACK relid. If not, this - * function emits a WARNING. + * Return whether userid has privileges to execute REPACK on relid. + * + * Caller may not have a lock on the relation, so it could have been + * dropped concurrently. In that case, silently return false. + * + * If the relation does exist but the user doesn't have the required + * privs, emit a WARNING and return false. Otherwise, return true. */ static bool repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) { bool is_missing = false; + AclResult result; + char *relname; Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK); - if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK) + result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing); + if (is_missing) + return false; + + if (result == ACLCHECK_OK) return true; - /* Report a warning if the relation still exists. */ - if (!is_missing) + /* + * The relation can also be dropped after we tested its ACL and before we + * read its relname, so be careful. + */ + relname = get_rel_name(relid); + if (relname != NULL) { - char *relname; - - relname = get_rel_name(relid); - if (relname != NULL) - { - ereport(WARNING, - errmsg("permission denied to execute %s on \"%s\", skipping it", - RepackCommandAsString(cmd), relname)); - - pfree(relname); - } + ereport(WARNING, + errmsg("permission denied to execute %s on \"%s\", skipping it", + RepackCommandAsString(cmd), relname)); + pfree(relname); } return false; -- 2.47.3 --op6mnexl7cn72cto-- ^ permalink raw reply [nested|flat] 152+ messages in thread
end of thread, other threads:[~2026-07-07 16:35 UTC | newest] Thread overview: 152+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-02-28 23:20 [PATCH v3 3/3] fixups Alvaro Herrera <[email protected]> 2023-05-16 13:42 [PATCH v10 3/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <[email protected]> 2023-05-16 13:42 [PATCH v9 2/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <[email protected]> 2023-11-16 02:30 [PATCH v1] Provide relfilenode statistics Bertrand Drouvot <[email protected]> 2026-02-09 16:51 [PATCH v7 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]> 2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox