agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v3 3/3] fixups 318+ messages / 4 participants [nested] [flat]
* [PATCH v3 3/3] fixups @ 2018-02-28 23:20 Alvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ 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; 318+ 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] 318+ 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; 318+ 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] 318+ messages in thread
* [PATCH v7 2/2] fixups @ 2026-02-09 16:51 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v43 7/7] Teach snapshot builder to skip transactions running REPACK (CONCURRENTLY). @ 2026-03-17 19:22 Antonin Houska <[email protected]> 0 siblings, 0 replies; 318+ messages in thread From: Antonin Houska @ 2026-03-17 19:22 UTC (permalink / raw) During logical decoding, we need to know if particular transaction performed catalog changes because catalog information is needed to construct heap tuples. To be sure that we have enough information of each transaction, the logical decoding cannot start before all the already running transactions have completed. The problem with REPACK (CONCURRENTLY) is that it has XID assigned and writes WAL records marked with it. Thus if another backend runs REPACK (CONCURRENTLY) and tries to setup the logical decoding, it has to wait for the completion of all the other transactions involved in REPACK (CONCURRENTLY). However, REPACK (CONCURRENTLY) does not perform any catalog changes relevant to logical decoding, so the other backends executing this command can ignore it. This patch implements it by adding information about transactions executing the command to the xl_running_xacts WAL record, and by teaching the snapshot builder to use the information. --- src/backend/access/rmgrdesc/standbydesc.c | 15 ++++- src/backend/access/transam/xlog.c | 2 + src/backend/commands/cluster.c | 16 +++++ src/backend/replication/logical/snapbuild.c | 38 ++++++++--- src/backend/storage/ipc/procarray.c | 73 +++++++++++++++++++-- src/backend/storage/ipc/standby.c | 6 +- src/include/access/xlog_internal.h | 2 +- src/include/storage/proc.h | 3 +- src/include/storage/standby.h | 3 + src/include/storage/standbydefs.h | 2 + 10 files changed, 138 insertions(+), 22 deletions(-) diff --git a/src/backend/access/rmgrdesc/standbydesc.c b/src/backend/access/rmgrdesc/standbydesc.c index 0a291354ae2..79c052d3607 100644 --- a/src/backend/access/rmgrdesc/standbydesc.c +++ b/src/backend/access/rmgrdesc/standbydesc.c @@ -21,10 +21,11 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec) { int i; - appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u", + appendStringInfo(buf, "nextXid %u latestCompletedXid %u oldestRunningXid %u oldestRunningXid %u", xlrec->nextXid, xlrec->latestCompletedXid, - xlrec->oldestRunningXid); + xlrec->oldestRunningXid, + xlrec->oldestRunningXidLogical); if (xlrec->xcnt > 0) { appendStringInfo(buf, "; %d xacts:", xlrec->xcnt); @@ -41,6 +42,16 @@ standby_desc_running_xacts(StringInfo buf, xl_running_xacts *xlrec) for (i = 0; i < xlrec->subxcnt; i++) appendStringInfo(buf, " %u", xlrec->xids[xlrec->xcnt + i]); } + + if (xlrec->xcnt_repack > 0) + { + TransactionId *xids_repack; + + appendStringInfo(buf, "; %d xacts_repack:", xlrec->xcnt_repack); + xids_repack = xlrec->xids + xlrec->xcnt + xlrec->subxcnt; + for (i = 0; i < xlrec->xcnt_repack; i++) + appendStringInfo(buf, " %u", xids_repack[i]); + } } void diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index f5c9a34374d..d050b0b1444 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5912,6 +5912,7 @@ StartupXLOG(void) * subxids are listed with their parent prepared transactions. */ running.xcnt = nxids; + running.xcnt_repack = 0; running.subxcnt = 0; running.subxid_status = SUBXIDS_IN_SUBTRANS; running.nextXid = XidFromFullTransactionId(checkPoint.nextXid); @@ -8477,6 +8478,7 @@ xlog_redo(XLogReaderState *record) * with their parent prepared transactions. */ running.xcnt = nxids; + running.xcnt_repack = 0; running.subxcnt = 0; running.subxid_status = SUBXIDS_IN_SUBTRANS; running.nextXid = XidFromFullTransactionId(checkPoint.nextXid); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 065316ee866..77c206ff944 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1084,6 +1084,22 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose, if (concurrent) { + /* + * Do not let other backends wait for our completion during their + * setup of logical replication. Unlike logical replication publisher, + * we will have XID assigned, so the other backends - whether + * walsenders involved in logical replication or regular backends + * executing also REPACK (CONCURRENTLY) - would have to wait for our + * completion before they can build their initial snapshot. It is + * o.k. for any decoding backend to ignore us because we do not change + * tuple descriptor of any table, and the data changes we write should + * not be decoded by other backends. + */ + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + MyProc->statusFlags |= PROC_IN_REPACK; + ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; + LWLockRelease(ProcArrayLock); + /* * The worker needs to be member of the locking group we're the leader * of. We ought to become the leader before the worker starts. The diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index 9cf499ce7c6..fbdd4600a2b 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -1172,7 +1172,7 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact * xmin, which looks odd but is correct and actually more efficient, since * we hit fast paths in heapam_visibility.c. */ - builder->xmin = running->oldestRunningXid; + builder->xmin = running->oldestRunningXidLogical; /* Remove transactions we don't need to keep track off anymore */ SnapBuildPurgeOlderTxn(builder); @@ -1188,9 +1188,9 @@ SnapBuildProcessRunningXacts(SnapBuild *builder, XLogRecPtr lsn, xl_running_xact */ xmin = ReorderBufferGetOldestXmin(builder->reorder); if (xmin == InvalidTransactionId) - xmin = running->oldestRunningXid; + xmin = running->oldestRunningXidLogical; elog(DEBUG3, "xmin: %u, xmax: %u, oldest running: %u, oldest xmin: %u", - builder->xmin, builder->xmax, running->oldestRunningXid, xmin); + builder->xmin, builder->xmax, running->oldestRunningXidLogical, xmin); LogicalIncreaseXminForSlot(lsn, xmin); /* @@ -1275,14 +1275,14 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn * have all necessary catalog rows anymore. */ if (TransactionIdIsNormal(builder->initial_xmin_horizon) && - NormalTransactionIdPrecedes(running->oldestRunningXid, + NormalTransactionIdPrecedes(running->oldestRunningXidLogical, builder->initial_xmin_horizon)) { ereport(DEBUG1, errmsg_internal("skipping snapshot at %X/%08X while building logical decoding snapshot, xmin horizon too low", LSN_FORMAT_ARGS(lsn)), errdetail_internal("initial xmin horizon of %u vs the snapshot's %u", - builder->initial_xmin_horizon, running->oldestRunningXid)); + builder->initial_xmin_horizon, running->oldestRunningXidLogical)); SnapBuildWaitSnapshot(running, builder->initial_xmin_horizon); @@ -1299,7 +1299,7 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn * NB: We might have already started to incrementally assemble a snapshot, * so we need to be careful to deal with that. */ - if (running->oldestRunningXid == running->nextXid) + if (running->oldestRunningXidLogical == running->nextXid) { if (!XLogRecPtrIsValid(builder->start_decoding_at) || builder->start_decoding_at <= lsn) @@ -1378,14 +1378,14 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn /* * c) transition from BUILDING_SNAPSHOT to FULL_SNAPSHOT. * - * In BUILDING_SNAPSHOT state, and this xl_running_xacts' oldestRunningXid + * In BUILDING_SNAPSHOT state, and this xl_running_xacts' oldestRunningXidLogical * is >= than nextXid from when we switched to BUILDING_SNAPSHOT. This * means all transactions starting afterwards have enough information to * be decoded. Switch to FULL_SNAPSHOT. */ else if (builder->state == SNAPBUILD_BUILDING_SNAPSHOT && TransactionIdPrecedesOrEquals(builder->next_phase_at, - running->oldestRunningXid)) + running->oldestRunningXidLogical)) { builder->state = SNAPBUILD_FULL_SNAPSHOT; builder->next_phase_at = running->nextXid; @@ -1402,14 +1402,14 @@ SnapBuildFindSnapshot(SnapBuild *builder, XLogRecPtr lsn, xl_running_xacts *runn /* * c) transition from FULL_SNAPSHOT to CONSISTENT. * - * In FULL_SNAPSHOT state, and this xl_running_xacts' oldestRunningXid is + * In FULL_SNAPSHOT state, and this xl_running_xacts' oldestRunningXidLogical is * >= than nextXid from when we switched to FULL_SNAPSHOT. This means all * transactions that are currently in progress have a catalog snapshot, * and all their changes have been collected. Switch to CONSISTENT. */ else if (builder->state == SNAPBUILD_FULL_SNAPSHOT && TransactionIdPrecedesOrEquals(builder->next_phase_at, - running->oldestRunningXid)) + running->oldestRunningXidLogical)) { builder->state = SNAPBUILD_CONSISTENT; builder->next_phase_at = InvalidTransactionId; @@ -1459,6 +1459,24 @@ SnapBuildWaitSnapshot(xl_running_xacts *running, TransactionId cutoff) if (TransactionIdFollows(xid, cutoff)) continue; + /* Do not wait for transactions running REPACK (CONCURRENTLY). */ + if (running->xcnt_repack > 0) + { + TransactionId *xids_repack; + int i; + + xids_repack = running->xids + running->xcnt + running->subxcnt; + + for (i = 0; i < running->xcnt_repack; i++) + { + if (xid == xids_repack[i]) + break; + } + /* Found? */ + if (i < running->xcnt_repack) + continue; + } + XactLockTableWait(xid, NULL, NULL, XLTW_None); } diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 0f913897acc..f1a03e6a754 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -2643,15 +2643,24 @@ GetRunningTransactionData(void) RunningTransactions CurrentRunningXacts = &CurrentRunningXactsData; TransactionId latestCompletedXid; TransactionId oldestRunningXid; + TransactionId oldestRunningXidLogical; TransactionId oldestDatabaseRunningXid; TransactionId *xids; int index; - int count; + int count, count_repack; int subcount; bool suboverflowed; + TransactionId *xids_repack = NULL; + bool logical_decoding_enabled = IsLogicalDecodingEnabled(); Assert(!RecoveryInProgress()); + /* + * TODO Consider a GUC to reserve certain amount of replication slots for + * REPACK (CONCURRENTLY) and using it here. + */ +#define MAX_REPACK_XIDS 16 + /* * Allocating space for maxProcs xids is usually overkill; numProcs would * be sufficient. But it seems better to do the malloc while not holding @@ -2663,11 +2672,13 @@ GetRunningTransactionData(void) */ if (CurrentRunningXacts->xids == NULL) { + int nrepack = logical_decoding_enabled ? MAX_REPACK_XIDS : 0; + /* * First call */ CurrentRunningXacts->xids = (TransactionId *) - malloc(TOTAL_MAX_CACHED_SUBXIDS * sizeof(TransactionId)); + malloc((TOTAL_MAX_CACHED_SUBXIDS + nrepack) * sizeof(TransactionId)); if (CurrentRunningXacts->xids == NULL) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), @@ -2676,7 +2687,10 @@ GetRunningTransactionData(void) xids = CurrentRunningXacts->xids; - count = subcount = 0; + if (logical_decoding_enabled) + xids_repack = palloc_array(TransactionId, MAX_REPACK_XIDS); + + count = subcount = count_repack = 0; suboverflowed = false; /* @@ -2688,7 +2702,7 @@ GetRunningTransactionData(void) latestCompletedXid = XidFromFullTransactionId(TransamVariables->latestCompletedXid); - oldestDatabaseRunningXid = oldestRunningXid = + oldestDatabaseRunningXid = oldestRunningXid = oldestRunningXidLogical = XidFromFullTransactionId(TransamVariables->nextXid); /* @@ -2697,6 +2711,8 @@ GetRunningTransactionData(void) for (index = 0; index < arrayP->numProcs; index++) { TransactionId xid; + int pgprocno; + PGPROC *proc; /* Fetch xid just once - see GetNewTransactionId */ xid = UINT32_ACCESS_ONCE(other_xids[index]); @@ -2716,6 +2732,21 @@ GetRunningTransactionData(void) if (TransactionIdPrecedes(xid, oldestRunningXid)) oldestRunningXid = xid; + if (logical_decoding_enabled && + TransactionIdPrecedes(xid, oldestRunningXidLogical)) + { + /* + * Backends running REPACK concurrently need to be excluded from + * oldestRunningXidLogical, otherwise the snapshot builder cannot + * proceed in building the initial snapshot. + */ + pgprocno = arrayP->pgprocnos[index]; + proc = &allProcs[pgprocno]; + + if ((proc->statusFlags & PROC_IN_REPACK) == 0) + oldestRunningXidLogical = xid; + } + /* * Also, update the oldest running xid within the current database. As * fetching pgprocno and PGPROC could cause cache misses, we do cheap @@ -2723,8 +2754,8 @@ GetRunningTransactionData(void) */ if (TransactionIdPrecedes(xid, oldestDatabaseRunningXid)) { - int pgprocno = arrayP->pgprocnos[index]; - PGPROC *proc = &allProcs[pgprocno]; + pgprocno = arrayP->pgprocnos[index]; + proc = &allProcs[pgprocno]; if (proc->databaseId == MyDatabaseId) oldestDatabaseRunningXid = xid; @@ -2742,6 +2773,19 @@ GetRunningTransactionData(void) */ xids[count++] = xid; + + /* + * Collect XIDSs of transactions running REPACK (CONCURRENTLY). + */ + if (logical_decoding_enabled && + count_repack < MAX_REPACK_XIDS) + { + pgprocno = arrayP->pgprocnos[index]; + proc = &allProcs[pgprocno]; + + if (proc->statusFlags & PROC_IN_REPACK) + xids_repack[count_repack++] = xid; + } } /* @@ -2782,6 +2826,19 @@ GetRunningTransactionData(void) } } + /* + * Append the XIDs running REPACK (CONCURRENTLY), if any. + * + * XXX Should we sort the array and use bsearch() when using it? + */ + if (count_repack > 0) + { + for (int i = 0; i < count_repack; i++) + xids[count++] = xids_repack[i]; + } + if (xids_repack) + pfree(xids_repack); + /* * It's important *not* to include the limits set by slots here because * snapbuild.c uses oldestRunningXid to manage its xmin horizon. If those @@ -2791,11 +2848,13 @@ GetRunningTransactionData(void) * increases if slots do. */ - CurrentRunningXacts->xcnt = count - subcount; + CurrentRunningXacts->xcnt = count - subcount - count_repack; CurrentRunningXacts->subxcnt = subcount; + CurrentRunningXacts->xcnt_repack = count_repack; CurrentRunningXacts->subxid_status = suboverflowed ? SUBXIDS_IN_SUBTRANS : SUBXIDS_IN_ARRAY; CurrentRunningXacts->nextXid = XidFromFullTransactionId(TransamVariables->nextXid); CurrentRunningXacts->oldestRunningXid = oldestRunningXid; + CurrentRunningXacts->oldestRunningXidLogical = oldestRunningXidLogical; CurrentRunningXacts->oldestDatabaseRunningXid = oldestDatabaseRunningXid; CurrentRunningXacts->latestCompletedXid = latestCompletedXid; diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c index f3ad90c7c7a..5c4121fcbc8 100644 --- a/src/backend/storage/ipc/standby.c +++ b/src/backend/storage/ipc/standby.c @@ -1188,6 +1188,7 @@ standby_redo(XLogReaderState *record) RunningTransactionsData running; running.xcnt = xlrec->xcnt; + running.xcnt_repack = xlrec->xcnt_repack; running.subxcnt = xlrec->subxcnt; running.subxid_status = xlrec->subxid_overflow ? SUBXIDS_MISSING : SUBXIDS_IN_ARRAY; running.nextXid = xlrec->nextXid; @@ -1358,10 +1359,12 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts) XLogRecPtr recptr; xlrec.xcnt = CurrRunningXacts->xcnt; + xlrec.xcnt_repack = CurrRunningXacts->xcnt_repack; xlrec.subxcnt = CurrRunningXacts->subxcnt; xlrec.subxid_overflow = (CurrRunningXacts->subxid_status != SUBXIDS_IN_ARRAY); xlrec.nextXid = CurrRunningXacts->nextXid; xlrec.oldestRunningXid = CurrRunningXacts->oldestRunningXid; + xlrec.oldestRunningXidLogical = CurrRunningXacts->oldestRunningXidLogical; xlrec.latestCompletedXid = CurrRunningXacts->latestCompletedXid; /* Header */ @@ -1372,7 +1375,8 @@ LogCurrentRunningXacts(RunningTransactions CurrRunningXacts) /* array of TransactionIds */ if (xlrec.xcnt > 0) XLogRegisterData(CurrRunningXacts->xids, - (xlrec.xcnt + xlrec.subxcnt) * sizeof(TransactionId)); + (xlrec.xcnt + xlrec.xcnt_repack + xlrec.subxcnt) * + sizeof(TransactionId)); recptr = XLogInsert(RM_STANDBY_ID, XLOG_RUNNING_XACTS); diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h index 629ac3a7d3e..1730b07810f 100644 --- a/src/include/access/xlog_internal.h +++ b/src/include/access/xlog_internal.h @@ -31,7 +31,7 @@ /* * Each page of XLOG file has a header like this: */ -#define XLOG_PAGE_MAGIC 0xD11D /* can be used as WAL version indicator */ +#define XLOG_PAGE_MAGIC 0xD11E /* can be used as WAL version indicator */ typedef struct XLogPageHeaderData { diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index bf3094f0f7d..120370f459d 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -67,10 +67,11 @@ struct XidCache #define PROC_AFFECTS_ALL_HORIZONS 0x20 /* this proc's xmin must be * included in vacuum horizons * in all databases */ +#define PROC_IN_REPACK 0x40 /* currently REPACK (CONCURRENTLY) */ /* flags reset at EOXact */ #define PROC_VACUUM_STATE_MASK \ - (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND) + (PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND | PROC_IN_REPACK) /* * Xmin-related flags. Make sure any flags that affect how the process' Xmin diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h index c63a4f2cc6a..0bd28e270f1 100644 --- a/src/include/storage/standby.h +++ b/src/include/storage/standby.h @@ -124,10 +124,13 @@ typedef enum typedef struct RunningTransactionsData { int xcnt; /* # of xact ids in xids[] */ + int xcnt_repack; /* # of xacts running REPACK + * (CONCURRENTLY). */ int subxcnt; /* # of subxact ids in xids[] */ subxids_array_status subxid_status; TransactionId nextXid; /* xid from TransamVariables->nextXid */ TransactionId oldestRunningXid; /* *not* oldestXmin */ + TransactionId oldestRunningXidLogical; TransactionId oldestDatabaseRunningXid; /* same as above, but within the * current database */ TransactionId latestCompletedXid; /* so we can set xmax */ diff --git a/src/include/storage/standbydefs.h b/src/include/storage/standbydefs.h index 231d251fd51..edad609fa9a 100644 --- a/src/include/storage/standbydefs.h +++ b/src/include/storage/standbydefs.h @@ -47,10 +47,12 @@ typedef struct xl_standby_locks typedef struct xl_running_xacts { int xcnt; /* # of xact ids in xids[] */ + int xcnt_repack; /* # of xacts running REPACK (CONCURRENTLY) */ int subxcnt; /* # of subxact ids in xids[] */ bool subxid_overflow; /* snapshot overflowed, subxids missing */ TransactionId nextXid; /* xid from TransamVariables->nextXid */ TransactionId oldestRunningXid; /* *not* oldestXmin */ + TransactionId oldestRunningXidLogical; TransactionId latestCompletedXid; /* so we can set xmax */ TransactionId xids[FLEXIBLE_ARRAY_MEMBER]; -- 2.47.3 --nzintiedl6o4kcyp-- ^ permalink raw reply [nested|flat] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
* [PATCH v2 2/2] fixups @ 2026-07-07 16:35 Álvaro Herrera <[email protected]> 0 siblings, 0 replies; 318+ 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] 318+ messages in thread
end of thread, other threads:[~2026-07-07 16:35 UTC | newest] Thread overview: 318+ 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 v9 2/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <[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]> 2026-02-09 16:51 [PATCH v7 2/2] fixups Álvaro Herrera <[email protected]> 2026-03-17 19:22 [PATCH v43 7/7] Teach snapshot builder to skip transactions running REPACK (CONCURRENTLY). Antonin Houska <[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]> 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]> 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