agora inbox for pgsql-hackers@postgresql.org  
help / color / mirror / Atom feed
[PATCH v3 3/3] fixups
327+ messages / 4 participants
[nested] [flat]

* [PATCH v3 3/3] fixups
@ 2018-02-28 23:20 Alvaro Herrera <alvherre@alvh.no-ip.org>
  0 siblings, 0 replies; 327+ 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] 327+ messages in thread

* [PATCH v10 3/3] Dedicated memory context for hash join spill buffers
@ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
  0 siblings, 0 replies; 327+ 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 <tomas.vondra@enterprisedb.com>
Author: Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
Reviewed-by:  Melanie Plageman <melanieplageman@gmail.com>
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] 327+ messages in thread

* [PATCH v9 2/3] Dedicated memory context for hash join spill buffers
@ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
  0 siblings, 0 replies; 327+ 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 <tomas.vondra@enterprisedb.com>
Author: Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
Reviewed-by:  Melanie Plageman <melanieplageman@gmail.com>
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] 327+ messages in thread

* [PATCH v10] Avoid orphaned objects dependencies
@ 2024-03-29 15:43 Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
  0 siblings, 0 replies; 327+ messages in thread

From: Bertrand Drouvot @ 2024-03-29 15:43 UTC (permalink / raw)

It's currently possible to create orphaned objects dependencies, for example:

Scenario 1:

session 1: begin; drop schema schem;
session 2: create a function in the schema schem
session 1: commit;

With the above, the function created in session 2 would be linked to a non
existing schema.

Scenario 2:

session 1: begin; create a function in the schema schem
session 2: drop schema schem;
session 1: commit;

With the above, the function created in session 1 would be linked to a non
existing schema.

To avoid those scenarios, a new lock (that conflicts with a lock taken by DROP)
has been put in place before the dependencies are being recorded. With this in
place, the drop schema in scenario 2 would be locked.

Also, after the new lock attempt, the patch checks that the object still exists:
with this in place session 2 in scenario 1 would be locked and would report an
error once session 1 committs (that would not be the case should session 1 abort
the transaction).

If the object is dropped before the new lock attempt is triggered then the patch
would also report an error (but with less details).

The patch adds a few tests for some dependency cases (that would currently produce
orphaned objects):

- schema and function (as the above scenarios)
- alter a dependency (function and schema)
- function and arg type
- function and return type
- function and function
- domain and domain
- table and type
- server and foreign data wrapper
---
 src/backend/catalog/aclchk.c                  |   1 +
 src/backend/catalog/dependency.c              | 109 ++++++++++++++-
 src/backend/catalog/heap.c                    |   8 ++
 src/backend/catalog/index.c                   |  16 +++
 src/backend/catalog/objectaddress.c           |  57 ++++++++
 src/backend/catalog/pg_aggregate.c            |   9 ++
 src/backend/catalog/pg_attrdef.c              |   1 +
 src/backend/catalog/pg_cast.c                 |   5 +
 src/backend/catalog/pg_collation.c            |   1 +
 src/backend/catalog/pg_constraint.c           |  11 ++
 src/backend/catalog/pg_conversion.c           |   2 +
 src/backend/catalog/pg_depend.c               |  27 +++-
 src/backend/catalog/pg_operator.c             |  19 +++
 src/backend/catalog/pg_proc.c                 |  17 ++-
 src/backend/catalog/pg_publication.c          |   5 +
 src/backend/catalog/pg_range.c                |   6 +
 src/backend/catalog/pg_type.c                 |  33 +++++
 src/backend/catalog/toasting.c                |   1 +
 src/backend/commands/alter.c                  |   4 +
 src/backend/commands/amcmds.c                 |   1 +
 src/backend/commands/cluster.c                |   5 +
 src/backend/commands/event_trigger.c          |   1 +
 src/backend/commands/extension.c              |   5 +
 src/backend/commands/foreigncmds.c            |   7 +
 src/backend/commands/functioncmds.c           |   6 +
 src/backend/commands/indexcmds.c              |   2 +
 src/backend/commands/opclasscmds.c            |  18 +++
 src/backend/commands/operatorcmds.c           |  30 ++++
 src/backend/commands/policy.c                 |   2 +
 src/backend/commands/proclang.c               |   3 +
 src/backend/commands/sequence.c               |   1 +
 src/backend/commands/statscmds.c              |   3 +
 src/backend/commands/tablecmds.c              |  32 +++--
 src/backend/commands/trigger.c                |  18 ++-
 src/backend/commands/tsearchcmds.c            |  81 +++++++----
 src/backend/commands/typecmds.c               |  84 ++++++++++++
 src/backend/rewrite/rewriteDefine.c           |   1 +
 src/backend/utils/errcodes.txt                |   1 +
 src/include/catalog/dependency.h              |   7 +
 src/include/catalog/objectaddress.h           |   1 +
 .../expected/test_dependencies_locks.out      | 129 ++++++++++++++++++
 src/test/isolation/isolation_schedule         |   1 +
 .../specs/test_dependencies_locks.spec        |  89 ++++++++++++
 43 files changed, 813 insertions(+), 47 deletions(-)
  31.4% src/backend/catalog/
  36.4% src/backend/commands/
  18.5% src/test/isolation/expected/
  11.5% src/test/isolation/specs/

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 143876b77f..5a614f25ff 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -1413,6 +1413,7 @@ SetDefaultACL(InternalDefaultACL *iacls)
 				referenced.objectId = iacls->nspid;
 				referenced.objectSubId = 0;
 
+				LockNotPinnedObject(NamespaceRelationId, iacls->nspid);
 				recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 			}
 		}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 0489cbabcb..abf8b92a6d 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -1519,6 +1519,84 @@ AcquireDeletionLock(const ObjectAddress *object, int flags)
 	}
 }
 
+/*
+ * LockNotPinnedObjectById
+ *
+ * Lock the object that we are about to record a dependency on.
+ * After it's locked, verify that it hasn't been dropped while we
+ * weren't looking.  If the object has been dropped, this function
+ * does not return!
+ */
+void
+LockNotPinnedObjectById(const ObjectAddress *object)
+{
+	char	   *object_description;
+
+	if (isObjectPinned(object))
+		return;
+
+	/*
+	 * Those don't rely on LockDatabaseObject() when being dropped (see
+	 * AcquireDeletionLock()). Also it looks like they can not produce
+	 * orphaned dependent objects when being dropped.
+	 */
+	if (object->classId == RelationRelationId || object->classId == AuthMemRelationId)
+		return;
+
+	object_description = getObjectDescription(object, true);
+
+	/* assume we should lock the whole object not a sub-object */
+	LockDatabaseObject(object->classId, object->objectId, 0, AccessShareLock);
+
+	/* check if object still exists */
+	if (!ObjectByIdExist(object))
+	{
+		if (object_description)
+			ereport(ERROR,
+					(errcode(ERRCODE_DEPENDENT_OBJECTS_DOES_NOT_EXIST),
+					 errmsg("%s does not exist", object_description)));
+		else
+			ereport(ERROR,
+					(errcode(ERRCODE_DEPENDENT_OBJECTS_DOES_NOT_EXIST),
+					 errmsg("a dependent object does not exist")));
+	}
+
+	if (object_description)
+		pfree(object_description);
+
+	return;
+}
+
+void
+LockNotPinnedObjectsById(const ObjectAddress *object, int nobject)
+{
+	int			i;
+
+	if (nobject < 0)
+		return;
+
+	for (i = 0; i < nobject; i++, object++)
+		LockNotPinnedObjectById(object);
+
+	return;
+}
+
+
+/*
+ * LockNotPinnedObject
+ *
+ * Lock the object that we are about to record a dependency on.
+ */
+void
+LockNotPinnedObject(Oid classid, Oid objid)
+{
+	ObjectAddress object;
+
+	ObjectAddressSet(object, classid, objid);
+
+	LockNotPinnedObjectById(&object);
+}
+
 /*
  * ReleaseDeletionLock - release an object deletion lock
  *
@@ -1564,13 +1642,8 @@ recordDependencyOnExpr(const ObjectAddress *depender,
 	/* Scan the expression tree for referenceable objects */
 	find_expr_references_walker(expr, &context);
 
-	/* Remove any duplicates */
-	eliminate_duplicate_dependencies(context.addrs);
-
-	/* And record 'em */
-	recordMultipleDependencies(depender,
-							   context.addrs->refs, context.addrs->numrefs,
-							   behavior);
+	/* Record all of them (this includes duplicate elimination) */
+	lock_record_object_address_dependencies(depender, context.addrs, behavior);
 
 	free_object_addresses(context.addrs);
 }
@@ -1654,14 +1727,19 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 
 		/* Record the self-dependencies with the appropriate direction */
 		if (!reverse_self)
+		{
+			LockNotPinnedObjectsById(self_addrs->refs, self_addrs->numrefs);
 			recordMultipleDependencies(depender,
 									   self_addrs->refs, self_addrs->numrefs,
 									   self_behavior);
+		}
 		else
 		{
 			/* Can't use recordMultipleDependencies, so do it the hard way */
 			int			selfref;
 
+			LockNotPinnedObjectById(depender);
+
 			for (selfref = 0; selfref < self_addrs->numrefs; selfref++)
 			{
 				ObjectAddress *thisobj = self_addrs->refs + selfref;
@@ -1674,6 +1752,7 @@ recordDependencyOnSingleRelExpr(const ObjectAddress *depender,
 	}
 
 	/* Record the external dependencies */
+	LockNotPinnedObjectsById(context.addrs->refs, context.addrs->numrefs);
 	recordMultipleDependencies(depender,
 							   context.addrs->refs, context.addrs->numrefs,
 							   behavior);
@@ -2734,6 +2813,22 @@ stack_address_present_add_flags(const ObjectAddress *object,
 	return result;
 }
 
+/*
+ * Record multiple dependencies from an ObjectAddresses array and lock the
+ * referenced objects, after first removing any duplicates.
+ */
+void
+lock_record_object_address_dependencies(const ObjectAddress *depender,
+										ObjectAddresses *referenced,
+										DependencyType behavior)
+{
+	eliminate_duplicate_dependencies(referenced);
+	LockNotPinnedObjectsById(referenced->refs, referenced->numrefs);
+	recordMultipleDependencies(depender,
+							   referenced->refs, referenced->numrefs,
+							   behavior);
+}
+
 /*
  * Record multiple dependencies from an ObjectAddresses array, after first
  * removing any duplicates.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index a122bbffce..72443710d3 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -843,6 +843,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 		ObjectAddressSubSet(myself, RelationRelationId, new_rel_oid, i + 1);
 		ObjectAddressSet(referenced, TypeRelationId,
 						 tupdesc->attrs[i].atttypid);
+		LockNotPinnedObject(TypeRelationId, tupdesc->attrs[i].atttypid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 		/* The default collation is pinned, so don't bother recording it */
@@ -851,6 +852,7 @@ AddNewAttributeTuples(Oid new_rel_oid,
 		{
 			ObjectAddressSet(referenced, CollationRelationId,
 							 tupdesc->attrs[i].attcollation);
+			LockNotPinnedObject(CollationRelationId, tupdesc->attrs[i].attcollation);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -1451,11 +1453,13 @@ heap_create_with_catalog(const char *relname,
 
 		ObjectAddressSet(referenced, NamespaceRelationId, relnamespace);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(NamespaceRelationId, relnamespace);
 
 		if (reloftypeid)
 		{
 			ObjectAddressSet(referenced, TypeRelationId, reloftypeid);
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(TypeRelationId, reloftypeid);
 		}
 
 		/*
@@ -1469,6 +1473,7 @@ heap_create_with_catalog(const char *relname,
 		{
 			ObjectAddressSet(referenced, AccessMethodRelationId, accessmtd);
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(AccessMethodRelationId, accessmtd);
 		}
 
 		record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
@@ -3383,6 +3388,7 @@ StorePartitionKey(Relation rel,
 	{
 		ObjectAddressSet(referenced, OperatorClassRelationId, partopclass[i]);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(OperatorClassRelationId, partopclass[i]);
 
 		/* The default collation is pinned, so don't bother recording it */
 		if (OidIsValid(partcollation[i]) &&
@@ -3390,6 +3396,7 @@ StorePartitionKey(Relation rel,
 		{
 			ObjectAddressSet(referenced, CollationRelationId, partcollation[i]);
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(CollationRelationId, partcollation[i]);
 		}
 	}
 
@@ -3409,6 +3416,7 @@ StorePartitionKey(Relation rel,
 
 		ObjectAddressSubSet(referenced, RelationRelationId,
 							RelationGetRelid(rel), partattrs[i]);
+		/* Do we need a lock on RelationRelationId? */
 		recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
 	}
 
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 55fdde4b24..b89c217a09 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -1127,6 +1127,7 @@ index_create(Relation heapRelation,
 										heapRelationId,
 										indexInfo->ii_IndexAttrNumbers[i]);
 					add_exact_object_address(&referenced, addrs);
+					/* XXX Do we need a lock for RelationRelationId? */
 					have_simple_col = true;
 				}
 			}
@@ -1141,6 +1142,7 @@ index_create(Relation heapRelation,
 			{
 				ObjectAddressSet(referenced, RelationRelationId,
 								 heapRelationId);
+				/* XXX Do we need a lock for RelationRelationId? */
 				add_exact_object_address(&referenced, addrs);
 			}
 
@@ -1157,9 +1159,11 @@ index_create(Relation heapRelation,
 		if (OidIsValid(parentIndexRelid))
 		{
 			ObjectAddressSet(referenced, RelationRelationId, parentIndexRelid);
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
 
 			ObjectAddressSet(referenced, RelationRelationId, heapRelationId);
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 
@@ -1175,6 +1179,7 @@ index_create(Relation heapRelation,
 			{
 				ObjectAddressSet(referenced, CollationRelationId, collationIds[i]);
 				add_exact_object_address(&referenced, addrs);
+				LockNotPinnedObject(CollationRelationId, collationIds[i]);
 			}
 		}
 
@@ -1183,6 +1188,7 @@ index_create(Relation heapRelation,
 		{
 			ObjectAddressSet(referenced, OperatorClassRelationId, opclassIds[i]);
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(OperatorClassRelationId, opclassIds[i]);
 		}
 
 		record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
@@ -1987,6 +1993,14 @@ index_constraint_create(Relation heapRelation,
 	 */
 	ObjectAddressSet(myself, ConstraintRelationId, conOid);
 	ObjectAddressSet(idxaddr, RelationRelationId, indexRelationId);
+
+	/*
+	 * CommandCounterIncrement here to ensure the new constraint entry is
+	 * visible when we'll check of object existence when recording the
+	 * dependencies.
+	 */
+	CommandCounterIncrement();
+	LockNotPinnedObject(ConstraintRelationId, conOid);
 	recordDependencyOn(&idxaddr, &myself, DEPENDENCY_INTERNAL);
 
 	/*
@@ -1998,9 +2012,11 @@ index_constraint_create(Relation heapRelation,
 		ObjectAddress referenced;
 
 		ObjectAddressSet(referenced, ConstraintRelationId, parentConstraintId);
+		LockNotPinnedObject(ConstraintRelationId, parentConstraintId);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
 		ObjectAddressSet(referenced, RelationRelationId,
 						 RelationGetRelid(heapRelation));
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 	}
 
diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c
index 7b536ac6fd..6d7abd3738 100644
--- a/src/backend/catalog/objectaddress.c
+++ b/src/backend/catalog/objectaddress.c
@@ -2590,6 +2590,63 @@ get_object_namespace(const ObjectAddress *address)
 	return oid;
 }
 
+/*
+ * ObjectByIdExist
+ *
+ * Return whether the given object exists.
+ *
+ * Works for most catalogs, if no special processing is needed.
+ */
+bool
+ObjectByIdExist(const ObjectAddress *address)
+{
+	HeapTuple	tuple;
+	int			cache;
+	const ObjectPropertyType *property;
+
+	property = get_object_property_data(address->classId);
+
+	cache = property->oid_catcache_id;
+
+	if (cache >= 0)
+	{
+		/* Fetch tuple from syscache. */
+		tuple = SearchSysCache1(cache, ObjectIdGetDatum(address->objectId));
+
+		if (!HeapTupleIsValid(tuple))
+		{
+			return false;
+		}
+
+		ReleaseSysCache(tuple);
+
+		return true;
+	}
+	else
+	{
+		Relation	rel;
+		ScanKeyData skey[1];
+		SysScanDesc scan;
+
+		rel = table_open(address->classId, AccessShareLock);
+
+		ScanKeyInit(&skey[0],
+					get_object_attnum_oid(address->classId),
+					BTEqualStrategyNumber, F_OIDEQ,
+					ObjectIdGetDatum(address->objectId));
+
+		scan = systable_beginscan(rel, get_object_oid_index(address->classId), true,
+								  NULL, 1, skey);
+
+		/* we expect exactly one match */
+		tuple = systable_getnext(scan);
+		systable_endscan(scan);
+		table_close(rel, AccessShareLock);
+
+		return (HeapTupleIsValid(tuple));
+	}
+}
+
 /*
  * Return ObjectType for the given object type as given by
  * getObjectTypeDescription; if no valid ObjectType code exists, but it's a
diff --git a/src/backend/catalog/pg_aggregate.c b/src/backend/catalog/pg_aggregate.c
index 90fc7db949..a47e3c5507 100644
--- a/src/backend/catalog/pg_aggregate.c
+++ b/src/backend/catalog/pg_aggregate.c
@@ -748,12 +748,14 @@ AggregateCreate(const char *aggName,
 	/* Depends on transition function */
 	ObjectAddressSet(referenced, ProcedureRelationId, transfn);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(ProcedureRelationId, transfn);
 
 	/* Depends on final function, if any */
 	if (OidIsValid(finalfn))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, finalfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, finalfn);
 	}
 
 	/* Depends on combine function, if any */
@@ -761,6 +763,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, combinefn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, combinefn);
 	}
 
 	/* Depends on serialization function, if any */
@@ -768,6 +771,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, serialfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, serialfn);
 	}
 
 	/* Depends on deserialization function, if any */
@@ -775,6 +779,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, deserialfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, deserialfn);
 	}
 
 	/* Depends on forward transition function, if any */
@@ -782,6 +787,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, mtransfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, mtransfn);
 	}
 
 	/* Depends on inverse transition function, if any */
@@ -789,6 +795,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, minvtransfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, minvtransfn);
 	}
 
 	/* Depends on final function, if any */
@@ -796,6 +803,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, mfinalfn);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, mfinalfn);
 	}
 
 	/* Depends on sort operator, if any */
@@ -803,6 +811,7 @@ AggregateCreate(const char *aggName,
 	{
 		ObjectAddressSet(referenced, OperatorRelationId, sortop);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(OperatorRelationId, sortop);
 	}
 
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
diff --git a/src/backend/catalog/pg_attrdef.c b/src/backend/catalog/pg_attrdef.c
index 003ae70b4d..80db2c0c20 100644
--- a/src/backend/catalog/pg_attrdef.c
+++ b/src/backend/catalog/pg_attrdef.c
@@ -178,6 +178,7 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	colobject.objectId = RelationGetRelid(rel);
 	colobject.objectSubId = attnum;
 
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&defobject, &colobject,
 					   attgenerated ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
 
diff --git a/src/backend/catalog/pg_cast.c b/src/backend/catalog/pg_cast.c
index 5a5b855d51..d3707e424c 100644
--- a/src/backend/catalog/pg_cast.c
+++ b/src/backend/catalog/pg_cast.c
@@ -97,16 +97,19 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
 	/* dependency on source type */
 	ObjectAddressSet(referenced, TypeRelationId, sourcetypeid);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(TypeRelationId, sourcetypeid);
 
 	/* dependency on target type */
 	ObjectAddressSet(referenced, TypeRelationId, targettypeid);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(TypeRelationId, targettypeid);
 
 	/* dependency on function */
 	if (OidIsValid(funcid))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, funcid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, funcid);
 	}
 
 	/* dependencies on casts required for function */
@@ -114,11 +117,13 @@ CastCreate(Oid sourcetypeid, Oid targettypeid,
 	{
 		ObjectAddressSet(referenced, CastRelationId, incastid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(CastRelationId, incastid);
 	}
 	if (OidIsValid(outcastid))
 	{
 		ObjectAddressSet(referenced, CastRelationId, outcastid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(CastRelationId, outcastid);
 	}
 
 	record_object_address_dependencies(&myself, addrs, behavior);
diff --git a/src/backend/catalog/pg_collation.c b/src/backend/catalog/pg_collation.c
index 7f2f701229..78498b8c20 100644
--- a/src/backend/catalog/pg_collation.c
+++ b/src/backend/catalog/pg_collation.c
@@ -218,6 +218,7 @@ CollationCreate(const char *collname, Oid collnamespace,
 	referenced.classId = NamespaceRelationId;
 	referenced.objectId = collnamespace;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(NamespaceRelationId, collnamespace);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* create dependency on owner */
diff --git a/src/backend/catalog/pg_constraint.c b/src/backend/catalog/pg_constraint.c
index 3baf9231ed..4afa9a1d69 100644
--- a/src/backend/catalog/pg_constraint.c
+++ b/src/backend/catalog/pg_constraint.c
@@ -257,12 +257,14 @@ CreateConstraintEntry(const char *constraintName,
 				ObjectAddressSubSet(relobject, RelationRelationId, relId,
 									constraintKey[i]);
 				add_exact_object_address(&relobject, addrs_auto);
+				/* XXX Do we need a lock for RelationRelationId?? */
 			}
 		}
 		else
 		{
 			ObjectAddressSet(relobject, RelationRelationId, relId);
 			add_exact_object_address(&relobject, addrs_auto);
+			/* XXX Do we need a lock for RelationRelationId?? */
 		}
 	}
 
@@ -275,6 +277,7 @@ CreateConstraintEntry(const char *constraintName,
 
 		ObjectAddressSet(domobject, TypeRelationId, domainId);
 		add_exact_object_address(&domobject, addrs_auto);
+		LockNotPinnedObject(TypeRelationId, domainId);
 	}
 
 	record_object_address_dependencies(&conobject, addrs_auto,
@@ -299,12 +302,14 @@ CreateConstraintEntry(const char *constraintName,
 				ObjectAddressSubSet(relobject, RelationRelationId,
 									foreignRelId, foreignKey[i]);
 				add_exact_object_address(&relobject, addrs_normal);
+				/* XXX Do we need a lock for RelationRelationId? */
 			}
 		}
 		else
 		{
 			ObjectAddressSet(relobject, RelationRelationId, foreignRelId);
 			add_exact_object_address(&relobject, addrs_normal);
+			/* XXX Do we need a lock for RelationRelationId? */
 		}
 	}
 
@@ -320,6 +325,7 @@ CreateConstraintEntry(const char *constraintName,
 
 		ObjectAddressSet(relobject, RelationRelationId, indexRelId);
 		add_exact_object_address(&relobject, addrs_normal);
+		/* XXX Do we need a lock for RelationRelationId?? */
 	}
 
 	if (foreignNKeys > 0)
@@ -339,15 +345,18 @@ CreateConstraintEntry(const char *constraintName,
 		{
 			oprobject.objectId = pfEqOp[i];
 			add_exact_object_address(&oprobject, addrs_normal);
+			LockNotPinnedObject(OperatorRelationId, pfEqOp[i]);
 			if (ppEqOp[i] != pfEqOp[i])
 			{
 				oprobject.objectId = ppEqOp[i];
 				add_exact_object_address(&oprobject, addrs_normal);
+				LockNotPinnedObject(OperatorRelationId, ppEqOp[i]);
 			}
 			if (ffEqOp[i] != pfEqOp[i])
 			{
 				oprobject.objectId = ffEqOp[i];
 				add_exact_object_address(&oprobject, addrs_normal);
+				LockNotPinnedObject(OperatorRelationId, ffEqOp[i]);
 			}
 		}
 	}
@@ -858,9 +867,11 @@ ConstraintSetParentConstraint(Oid childConstrId,
 		ObjectAddressSet(depender, ConstraintRelationId, childConstrId);
 
 		ObjectAddressSet(referenced, ConstraintRelationId, parentConstrId);
+		LockNotPinnedObject(ConstraintRelationId, parentConstrId);
 		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_PRI);
 
 		ObjectAddressSet(referenced, RelationRelationId, childTableId);
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_SEC);
 	}
 	else
diff --git a/src/backend/catalog/pg_conversion.c b/src/backend/catalog/pg_conversion.c
index 0770878eac..25881654d6 100644
--- a/src/backend/catalog/pg_conversion.c
+++ b/src/backend/catalog/pg_conversion.c
@@ -116,12 +116,14 @@ ConversionCreate(const char *conname, Oid connamespace,
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = conproc;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ProcedureRelationId, conproc);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* create dependency on namespace */
 	referenced.classId = NamespaceRelationId;
 	referenced.objectId = connamespace;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(NamespaceRelationId, connamespace);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* create dependency on owner */
diff --git a/src/backend/catalog/pg_depend.c b/src/backend/catalog/pg_depend.c
index cfd7ef51df..b97aa2ab91 100644
--- a/src/backend/catalog/pg_depend.c
+++ b/src/backend/catalog/pg_depend.c
@@ -20,21 +20,20 @@
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_auth_members.h"
 #include "catalog/pg_constraint.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
 #include "catalog/partition.h"
 #include "commands/extension.h"
 #include "miscadmin.h"
+#include "storage/lock.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
 #include "utils/rel.h"
 
 
-static bool isObjectPinned(const ObjectAddress *object);
-
-
 /*
  * Record a dependency between 2 objects via their respective objectAddress.
  * The first argument is the dependent object, the second the one it
@@ -100,6 +99,25 @@ recordMultipleDependencies(const ObjectAddress *depender,
 	slot_init_count = 0;
 	for (i = 0; i < nreferenced; i++, referenced++)
 	{
+#ifdef USE_ASSERT_CHECKING
+		LOCKTAG		tag;
+
+		SET_LOCKTAG_OBJECT(tag,
+						   MyDatabaseId,
+						   referenced->classId,
+						   referenced->objectId,
+						   0);
+
+		/*
+		 * Assert the referenced object is locked (see
+		 * LockNotPinnedObjectById()).
+		 */
+		Assert(isObjectPinned(referenced) ||
+			   referenced->classId == RelationRelationId ||
+			   referenced->classId == AuthMemRelationId ||
+			   LockHeldByMe(&tag, AccessShareLock));
+#endif
+
 		/*
 		 * If the referenced object is pinned by the system, there's no real
 		 * need to record dependencies on it.  This saves lots of space in
@@ -239,6 +257,7 @@ recordDependencyOnCurrentExtension(const ObjectAddress *object,
 		extension.objectId = CurrentExtensionObject;
 		extension.objectSubId = 0;
 
+		LockNotPinnedObject(ExtensionRelationId, CurrentExtensionObject);
 		recordDependencyOn(object, &extension, DEPENDENCY_EXTENSION);
 	}
 }
@@ -706,7 +725,7 @@ changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
  * The passed subId, if any, is ignored; we assume that only whole objects
  * are pinned (and that this implies pinning their components).
  */
-static bool
+bool
 isObjectPinned(const ObjectAddress *object)
 {
 	return IsPinnedObject(object->classId, object->objectId);
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 65b45a424a..e8374eec88 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -251,6 +251,16 @@ OperatorShellMake(const char *operatorName,
 	values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(InvalidOid);
 	values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(InvalidOid);
 
+	/* Lock dependent objects */
+	if (OidIsValid(operatorNamespace))
+		LockNotPinnedObject(NamespaceRelationId, operatorNamespace);
+
+	if (OidIsValid(leftTypeId))
+		LockNotPinnedObject(TypeRelationId, leftTypeId);
+
+	if (OidIsValid(rightTypeId))
+		LockNotPinnedObject(TypeRelationId, rightTypeId);
+
 	/*
 	 * create a new operator tuple
 	 */
@@ -513,6 +523,15 @@ OperatorCreate(const char *operatorName,
 		CatalogTupleInsert(pg_operator_desc, tup);
 	}
 
+	/* Lock dependent objects */
+	LockNotPinnedObject(NamespaceRelationId, operatorNamespace);
+	LockNotPinnedObject(TypeRelationId, leftTypeId);
+	LockNotPinnedObject(TypeRelationId, rightTypeId);
+	LockNotPinnedObject(TypeRelationId, operResultType);
+	LockNotPinnedObject(ProcedureRelationId, procedureId);
+	LockNotPinnedObject(ProcedureRelationId, restrictionId);
+	LockNotPinnedObject(ProcedureRelationId, joinId);
+
 	/* Add dependencies for the entry */
 	address = makeOperatorDependencies(tup, true, isUpdate);
 
diff --git a/src/backend/catalog/pg_proc.c b/src/backend/catalog/pg_proc.c
index 528c17cd7f..9cceb6413b 100644
--- a/src/backend/catalog/pg_proc.c
+++ b/src/backend/catalog/pg_proc.c
@@ -593,6 +593,13 @@ ProcedureCreate(const char *procedureName,
 	if (is_update)
 		deleteDependencyRecordsFor(ProcedureRelationId, retval, true);
 
+	/*
+	 * CommandCounterIncrement here to ensure the new function entry is
+	 * visible when we'll check of object existence when recording the
+	 * dependencies.
+	 */
+	CommandCounterIncrement();
+
 	addrs = new_object_addresses();
 
 	ObjectAddressSet(myself, ProcedureRelationId, retval);
@@ -600,20 +607,24 @@ ProcedureCreate(const char *procedureName,
 	/* dependency on namespace */
 	ObjectAddressSet(referenced, NamespaceRelationId, procNamespace);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(NamespaceRelationId, procNamespace);
 
 	/* dependency on implementation language */
 	ObjectAddressSet(referenced, LanguageRelationId, languageObjectId);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(LanguageRelationId, languageObjectId);
 
 	/* dependency on return type */
 	ObjectAddressSet(referenced, TypeRelationId, returnType);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(TypeRelationId, returnType);
 
 	/* dependency on transform used by return type, if any */
 	if ((trfid = get_transform_oid(returnType, languageObjectId, true)))
 	{
 		ObjectAddressSet(referenced, TransformRelationId, trfid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(TransformRelationId, trfid);
 	}
 
 	/* dependency on parameter types */
@@ -621,12 +632,14 @@ ProcedureCreate(const char *procedureName,
 	{
 		ObjectAddressSet(referenced, TypeRelationId, allParams[i]);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(TypeRelationId, allParams[i]);
 
 		/* dependency on transform used by parameter type, if any */
 		if ((trfid = get_transform_oid(allParams[i], languageObjectId, true)))
 		{
 			ObjectAddressSet(referenced, TransformRelationId, trfid);
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(TransformRelationId, trfid);
 		}
 	}
 
@@ -635,6 +648,7 @@ ProcedureCreate(const char *procedureName,
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, prosupport);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, prosupport);
 	}
 
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
@@ -674,9 +688,6 @@ ProcedureCreate(const char *procedureName,
 		ArrayType  *set_items = NULL;
 		int			save_nestlevel = 0;
 
-		/* Advance command counter so new tuple can be seen by validator */
-		CommandCounterIncrement();
-
 		/*
 		 * Set per-function configuration parameters so that the validation is
 		 * done with the environment the function expects.  However, if
diff --git a/src/backend/catalog/pg_publication.c b/src/backend/catalog/pg_publication.c
index 0602398a54..5e16c0fcef 100644
--- a/src/backend/catalog/pg_publication.c
+++ b/src/backend/catalog/pg_publication.c
@@ -438,10 +438,12 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
 
 	/* Add dependency on the publication */
 	ObjectAddressSet(referenced, PublicationRelationId, pubid);
+	LockNotPinnedObject(PublicationRelationId, pubid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* Add dependency on the relation */
 	ObjectAddressSet(referenced, RelationRelationId, relid);
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* Add dependency on the objects mentioned in the qualifications */
@@ -454,6 +456,7 @@ publication_add_relation(Oid pubid, PublicationRelInfo *pri,
 	for (int i = 0; i < natts; i++)
 	{
 		ObjectAddressSubSet(referenced, RelationRelationId, relid, attarray[i]);
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -661,10 +664,12 @@ publication_add_schema(Oid pubid, Oid schemaid, bool if_not_exists)
 
 	/* Add dependency on the publication */
 	ObjectAddressSet(referenced, PublicationRelationId, pubid);
+	LockNotPinnedObject(PublicationRelationId, pubid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* Add dependency on the schema */
 	ObjectAddressSet(referenced, NamespaceRelationId, schemaid);
+	LockNotPinnedObject(NamespaceRelationId, schemaid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* Close the table */
diff --git a/src/backend/catalog/pg_range.c b/src/backend/catalog/pg_range.c
index 501a6ba410..e5b5a0b6f8 100644
--- a/src/backend/catalog/pg_range.c
+++ b/src/backend/catalog/pg_range.c
@@ -70,26 +70,31 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 
 	ObjectAddressSet(referenced, TypeRelationId, rangeSubType);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(TypeRelationId, rangeSubType);
 
 	ObjectAddressSet(referenced, OperatorClassRelationId, rangeSubOpclass);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(OperatorClassRelationId, rangeSubOpclass);
 
 	if (OidIsValid(rangeCollation))
 	{
 		ObjectAddressSet(referenced, CollationRelationId, rangeCollation);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(CollationRelationId, rangeCollation);
 	}
 
 	if (OidIsValid(rangeCanonical))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, rangeCanonical);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, rangeCanonical);
 	}
 
 	if (OidIsValid(rangeSubDiff))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, rangeSubDiff);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, rangeSubDiff);
 	}
 
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
@@ -99,6 +104,7 @@ RangeCreate(Oid rangeTypeOid, Oid rangeSubType, Oid rangeCollation,
 	referencing.classId = TypeRelationId;
 	referencing.objectId = multirangeTypeOid;
 	referencing.objectSubId = 0;
+	LockNotPinnedObject(TypeRelationId, rangeTypeOid);
 	recordDependencyOn(&referencing, &myself, DEPENDENCY_INTERNAL);
 
 	table_close(pg_range, RowExclusiveLock);
diff --git a/src/backend/catalog/pg_type.c b/src/backend/catalog/pg_type.c
index 395dec8ed8..92614cdaaa 100644
--- a/src/backend/catalog/pg_type.c
+++ b/src/backend/catalog/pg_type.c
@@ -157,6 +157,12 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
 	 */
 	if (!IsBootstrapProcessingMode())
+	{
+		/* Lock dependent objects */
+		LockNotPinnedObject(NamespaceRelationId, typeNamespace);
+		LockNotPinnedObject(ProcedureRelationId, F_SHELL_IN);
+		LockNotPinnedObject(ProcedureRelationId, F_SHELL_OUT);
+
 		GenerateTypeDependencies(tup,
 								 pg_type_desc,
 								 NULL,
@@ -166,6 +172,7 @@ TypeShellMake(const char *typeName, Oid typeNamespace, Oid ownerId)
 								 false,
 								 true,	/* make extension dependency */
 								 false);
+	}
 
 	/* Post creation hook for new shell type */
 	InvokeObjectPostCreateHook(TypeRelationId, typoid, 0);
@@ -494,6 +501,31 @@ TypeCreate(Oid newTypeOid,
 	 * Create dependencies.  We can/must skip this in bootstrap mode.
 	 */
 	if (!IsBootstrapProcessingMode())
+	{
+		/*
+		 * CommandCounterIncrement here to ensure the new type  entry is
+		 * visible when we'll check of object existence when recording the
+		 * dependencies.
+		 */
+		CommandCounterIncrement();
+
+		/* Lock dependent objects */
+		LockNotPinnedObject(NamespaceRelationId, typeNamespace);
+		LockNotPinnedObject(ProcedureRelationId, inputProcedure);
+		LockNotPinnedObject(ProcedureRelationId, outputProcedure);
+		LockNotPinnedObject(ProcedureRelationId, receiveProcedure);
+		LockNotPinnedObject(ProcedureRelationId, sendProcedure);
+		LockNotPinnedObject(ProcedureRelationId, typmodinProcedure);
+		LockNotPinnedObject(ProcedureRelationId, typmodoutProcedure);
+		LockNotPinnedObject(ProcedureRelationId, analyzeProcedure);
+		LockNotPinnedObject(ProcedureRelationId, subscriptProcedure);
+		LockNotPinnedObject(TypeRelationId, baseType);
+		LockNotPinnedObject(CollationRelationId, typeCollation);
+		LockNotPinnedObject(TypeRelationId, elementType);
+		/* XXX Do we need a lock for RelationRelationId? */
+		if (relationKind == RELKIND_COMPOSITE_TYPE)
+			LockNotPinnedObject(TypeRelationId, typeObjectId);
+
 		GenerateTypeDependencies(tup,
 								 pg_type_desc,
 								 (defaultTypeBin ?
@@ -505,6 +537,7 @@ TypeCreate(Oid newTypeOid,
 								 isDependentType,
 								 true,	/* make extension dependency */
 								 rebuildDeps);
+	}
 
 	/* Post creation hook for new type */
 	InvokeObjectPostCreateHook(TypeRelationId, typeObjectId, 0);
diff --git a/src/backend/catalog/toasting.c b/src/backend/catalog/toasting.c
index 738bc46ae8..4a708030ac 100644
--- a/src/backend/catalog/toasting.c
+++ b/src/backend/catalog/toasting.c
@@ -367,6 +367,7 @@ create_toast_table(Relation rel, Oid toastOid, Oid toastIndexOid,
 		toastobject.objectId = toast_relid;
 		toastobject.objectSubId = 0;
 
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&toastobject, &baseobject, DEPENDENCY_INTERNAL);
 	}
 
diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c
index 4f99ebb447..57e86f576a 100644
--- a/src/backend/commands/alter.c
+++ b/src/backend/commands/alter.c
@@ -501,7 +501,10 @@ ExecAlterObjectDependsStmt(AlterObjectDependsStmt *stmt, ObjectAddress *refAddre
 		currexts = getAutoExtensionsOfObject(address.classId,
 											 address.objectId);
 		if (!list_member_oid(currexts, refAddr.objectId))
+		{
+			LockNotPinnedObject(refAddr.classId, refAddr.objectId);
 			recordDependencyOn(&address, &refAddr, DEPENDENCY_AUTO_EXTENSION);
+		}
 	}
 
 	return address;
@@ -807,6 +810,7 @@ AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid)
 	pfree(replaces);
 
 	/* update dependency to point to the new schema */
+	LockNotPinnedObject(NamespaceRelationId, nspOid);
 	if (changeDependencyFor(classId, objid,
 							NamespaceRelationId, oldNspOid, nspOid) != 1)
 		elog(ERROR, "could not change schema dependency for object %u",
diff --git a/src/backend/commands/amcmds.c b/src/backend/commands/amcmds.c
index aaa0f9a1dc..8616a7c9fa 100644
--- a/src/backend/commands/amcmds.c
+++ b/src/backend/commands/amcmds.c
@@ -104,6 +104,7 @@ CreateAccessMethod(CreateAmStmt *stmt)
 	referenced.objectId = amhandler;
 	referenced.objectSubId = 0;
 
+	LockNotPinnedObject(ProcedureRelationId, amhandler);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	recordDependencyOnCurrentExtension(&myself, false);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 78f96789b0..b54a04f4b0 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -1272,6 +1272,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 	 */
 	if (relam1 != relam2)
 	{
+		LockNotPinnedObject(AccessMethodRelationId, relam2);
 		if (changeDependencyFor(RelationRelationId,
 								r1,
 								AccessMethodRelationId,
@@ -1280,6 +1281,8 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 			elog(ERROR, "could not change access method dependency for relation \"%s.%s\"",
 				 get_namespace_name(get_rel_namespace(r1)),
 				 get_rel_name(r1));
+
+		LockNotPinnedObject(AccessMethodRelationId, relam1);
 		if (changeDependencyFor(RelationRelationId,
 								r2,
 								AccessMethodRelationId,
@@ -1381,6 +1384,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 			{
 				baseobject.objectId = r1;
 				toastobject.objectId = relform1->reltoastrelid;
+				/* XXX Do we need a lock for RelationRelationId? */
 				recordDependencyOn(&toastobject, &baseobject,
 								   DEPENDENCY_INTERNAL);
 			}
@@ -1389,6 +1393,7 @@ swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
 			{
 				baseobject.objectId = r2;
 				toastobject.objectId = relform2->reltoastrelid;
+				/* XXX Do we need a lock for RelationRelationId? */
 				recordDependencyOn(&toastobject, &baseobject,
 								   DEPENDENCY_INTERNAL);
 			}
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 7a5ed6b985..8d0cdec59e 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -327,6 +327,7 @@ insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtO
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ProcedureRelationId, funcoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* Depend on extension, if any. */
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 1643c8c69a..669a5d6dd8 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1924,6 +1924,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 
 	ObjectAddressSet(nsp, NamespaceRelationId, schemaOid);
 	add_exact_object_address(&nsp, refobjs);
+	LockNotPinnedObject(NamespaceRelationId, schemaOid);
 
 	foreach(lc, requiredExtensions)
 	{
@@ -1932,6 +1933,7 @@ InsertExtensionTuple(const char *extName, Oid extOwner,
 
 		ObjectAddressSet(otherext, ExtensionRelationId, reqext);
 		add_exact_object_address(&otherext, refobjs);
+		LockNotPinnedObject(ExtensionRelationId, reqext);
 	}
 
 	/* Record all of them (this includes duplicate elimination) */
@@ -2968,6 +2970,7 @@ AlterExtensionNamespace(const char *extensionName, const char *newschema, Oid *o
 	table_close(extRel, RowExclusiveLock);
 
 	/* update dependency to point to the new schema */
+	LockNotPinnedObject(NamespaceRelationId, nspOid);
 	if (changeDependencyFor(ExtensionRelationId, extensionOid,
 							NamespaceRelationId, oldNspOid, nspOid) != 1)
 		elog(ERROR, "could not change schema dependency for extension %s",
@@ -3258,6 +3261,7 @@ ApplyExtensionUpdates(Oid extensionOid,
 			otherext.objectId = reqext;
 			otherext.objectSubId = 0;
 
+			LockNotPinnedObject(ExtensionRelationId, reqext);
 			recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
 		}
 
@@ -3414,6 +3418,7 @@ ExecAlterExtensionContentsRecurse(AlterExtensionContentsStmt *stmt,
 		/*
 		 * OK, add the dependency.
 		 */
+		LockNotPinnedObject(extension.classId, extension.objectId);
 		recordDependencyOn(&object, &extension, DEPENDENCY_EXTENSION);
 
 		/*
diff --git a/src/backend/commands/foreigncmds.c b/src/backend/commands/foreigncmds.c
index cf61bbac1f..735bca486c 100644
--- a/src/backend/commands/foreigncmds.c
+++ b/src/backend/commands/foreigncmds.c
@@ -642,6 +642,7 @@ CreateForeignDataWrapper(ParseState *pstate, CreateFdwStmt *stmt)
 		referenced.classId = ProcedureRelationId;
 		referenced.objectId = fdwhandler;
 		referenced.objectSubId = 0;
+		LockNotPinnedObject(ProcedureRelationId, fdwhandler);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -650,6 +651,7 @@ CreateForeignDataWrapper(ParseState *pstate, CreateFdwStmt *stmt)
 		referenced.classId = ProcedureRelationId;
 		referenced.objectId = fdwvalidator;
 		referenced.objectSubId = 0;
+		LockNotPinnedObject(ProcedureRelationId, fdwvalidator);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -811,6 +813,7 @@ AlterForeignDataWrapper(ParseState *pstate, AlterFdwStmt *stmt)
 			referenced.classId = ProcedureRelationId;
 			referenced.objectId = fdwhandler;
 			referenced.objectSubId = 0;
+			LockNotPinnedObject(ProcedureRelationId, fdwhandler);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 
@@ -819,6 +822,7 @@ AlterForeignDataWrapper(ParseState *pstate, AlterFdwStmt *stmt)
 			referenced.classId = ProcedureRelationId;
 			referenced.objectId = fdwvalidator;
 			referenced.objectSubId = 0;
+			LockNotPinnedObject(ProcedureRelationId, fdwvalidator);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -951,6 +955,7 @@ CreateForeignServer(CreateForeignServerStmt *stmt)
 	referenced.classId = ForeignDataWrapperRelationId;
 	referenced.objectId = fdw->fdwid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ForeignDataWrapperRelationId, fdw->fdwid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId);
@@ -1195,6 +1200,7 @@ CreateUserMapping(CreateUserMappingStmt *stmt)
 	referenced.classId = ForeignServerRelationId;
 	referenced.objectId = srv->serverid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ForeignServerRelationId, srv->serverid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	if (OidIsValid(useId))
@@ -1472,6 +1478,7 @@ CreateForeignTable(CreateForeignTableStmt *stmt, Oid relid)
 	referenced.classId = ForeignServerRelationId;
 	referenced.objectId = server->serverid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ForeignServerRelationId, server->serverid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	table_close(ftrel, RowExclusiveLock);
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index 6593fd7d81..8207ef08b3 100644
--- a/src/backend/commands/functioncmds.c
+++ b/src/backend/commands/functioncmds.c
@@ -1446,6 +1446,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 		/* Add or replace dependency on support function */
 		if (OidIsValid(procForm->prosupport))
 		{
+			LockNotPinnedObject(ProcedureRelationId, newsupport);
 			if (changeDependencyFor(ProcedureRelationId, funcOid,
 									ProcedureRelationId, procForm->prosupport,
 									newsupport) != 1)
@@ -1459,6 +1460,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
 			referenced.classId = ProcedureRelationId;
 			referenced.objectId = newsupport;
 			referenced.objectSubId = 0;
+			LockNotPinnedObject(ProcedureRelationId, newsupport);
 			recordDependencyOn(&address, &referenced, DEPENDENCY_NORMAL);
 		}
 
@@ -1962,21 +1964,25 @@ CreateTransform(CreateTransformStmt *stmt)
 	/* dependency on language */
 	ObjectAddressSet(referenced, LanguageRelationId, langid);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(LanguageRelationId, langid);
 
 	/* dependency on type */
 	ObjectAddressSet(referenced, TypeRelationId, typeid);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(TypeRelationId, typeid);
 
 	/* dependencies on functions */
 	if (OidIsValid(fromsqlfuncid))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, fromsqlfuncid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, fromsqlfuncid);
 	}
 	if (OidIsValid(tosqlfuncid))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, tosqlfuncid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, tosqlfuncid);
 	}
 
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 309389e20d..b14eaad7a3 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4377,8 +4377,10 @@ IndexSetParentIndex(Relation partitionIdx, Oid parentOid)
 			ObjectAddressSet(parentIdx, RelationRelationId, parentOid);
 			ObjectAddressSet(partitionTbl, RelationRelationId,
 							 partitionIdx->rd_index->indrelid);
+			/* Do we lock for RelationRelationId?? */
 			recordDependencyOn(&partIdx, &parentIdx,
 							   DEPENDENCY_PARTITION_PRI);
+			/* Do we lock for RelationRelationId?? */
 			recordDependencyOn(&partIdx, &partitionTbl,
 							   DEPENDENCY_PARTITION_SEC);
 		}
diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c
index b8b5c147c5..ca15106ca9 100644
--- a/src/backend/commands/opclasscmds.c
+++ b/src/backend/commands/opclasscmds.c
@@ -298,12 +298,14 @@ CreateOpFamily(CreateOpFamilyStmt *stmt, const char *opfname,
 	referenced.classId = AccessMethodRelationId;
 	referenced.objectId = amoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(AccessMethodRelationId, amoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* dependency on namespace */
 	referenced.classId = NamespaceRelationId;
 	referenced.objectId = namespaceoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on owner */
@@ -725,18 +727,21 @@ DefineOpClass(CreateOpClassStmt *stmt)
 	referenced.classId = NamespaceRelationId;
 	referenced.objectId = namespaceoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on opfamily */
 	referenced.classId = OperatorFamilyRelationId;
 	referenced.objectId = opfamilyoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(OperatorFamilyRelationId, opfamilyoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 	/* dependency on indexed datatype */
 	referenced.classId = TypeRelationId;
 	referenced.objectId = typeoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(TypeRelationId, typeoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	/* dependency on storage datatype */
@@ -745,6 +750,7 @@ DefineOpClass(CreateOpClassStmt *stmt)
 		referenced.classId = TypeRelationId;
 		referenced.objectId = storageoid;
 		referenced.objectSubId = 0;
+		LockNotPinnedObject(TypeRelationId, storageoid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 	}
 
@@ -1486,6 +1492,13 @@ storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 
 		heap_freetuple(tup);
 
+		/*
+		 * CommandCounterIncrement here to ensure the new operator entry is
+		 * visible when we'll check of object existence when recording the
+		 * dependencies.
+		 */
+		CommandCounterIncrement();
+
 		/* Make its dependencies */
 		myself.classId = AccessMethodOperatorRelationId;
 		myself.objectId = entryoid;
@@ -1496,6 +1509,7 @@ storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 		referenced.objectSubId = 0;
 
 		/* see comments in amapi.h about dependency strength */
+		LockNotPinnedObject(OperatorRelationId, op->object);
 		recordDependencyOn(&myself, &referenced,
 						   op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
 
@@ -1504,6 +1518,7 @@ storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 		referenced.objectId = op->refobjid;
 		referenced.objectSubId = 0;
 
+		LockNotPinnedObject(referenced.classId, op->refobjid);
 		recordDependencyOn(&myself, &referenced,
 						   op->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
 
@@ -1514,6 +1529,7 @@ storeOperators(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 			referenced.objectId = op->sortfamily;
 			referenced.objectSubId = 0;
 
+			LockNotPinnedObject(OperatorFamilyRelationId, op->sortfamily);
 			recordDependencyOn(&myself, &referenced,
 							   op->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
 		}
@@ -1597,6 +1613,7 @@ storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 		referenced.objectSubId = 0;
 
 		/* see comments in amapi.h about dependency strength */
+		LockNotPinnedObject(ProcedureRelationId, proc->object);
 		recordDependencyOn(&myself, &referenced,
 						   proc->ref_is_hard ? DEPENDENCY_NORMAL : DEPENDENCY_AUTO);
 
@@ -1605,6 +1622,7 @@ storeProcedures(List *opfamilyname, Oid amoid, Oid opfamilyoid,
 		referenced.objectId = proc->refobjid;
 		referenced.objectSubId = 0;
 
+		LockNotPinnedObject(referenced.classId, proc->refobjid);
 		recordDependencyOn(&myself, &referenced,
 						   proc->ref_is_hard ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
 
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index 5872a3e192..58a69e7cc2 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -33,6 +33,7 @@
 
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
@@ -656,11 +657,15 @@ AlterOperator(AlterOperatorStmt *stmt)
 	{
 		replaces[Anum_pg_operator_oprrest - 1] = true;
 		values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionOid);
+		if (OidIsValid(restrictionOid))
+			LockNotPinnedObject(ProcedureRelationId, restrictionOid);
 	}
 	if (updateJoin)
 	{
 		replaces[Anum_pg_operator_oprjoin - 1] = true;
 		values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinOid);
+		if (OidIsValid(joinOid))
+			LockNotPinnedObject(ProcedureRelationId, joinOid);
 	}
 	if (OidIsValid(commutatorOid))
 	{
@@ -688,6 +693,31 @@ AlterOperator(AlterOperatorStmt *stmt)
 
 	CatalogTupleUpdate(catalog, &tup->t_self, tup);
 
+
+	/* Lock dependent objects */
+	oprForm = (Form_pg_operator) GETSTRUCT(tup);
+
+	if (OidIsValid(oprForm->oprnamespace))
+		LockNotPinnedObject(NamespaceRelationId, oprForm->oprnamespace);
+
+	if (OidIsValid(oprForm->oprleft))
+		LockNotPinnedObject(TypeRelationId, oprForm->oprleft);
+
+	if (OidIsValid(oprForm->oprright))
+		LockNotPinnedObject(TypeRelationId, oprForm->oprright);
+
+	if (OidIsValid(oprForm->oprresult))
+		LockNotPinnedObject(TypeRelationId, oprForm->oprresult);
+
+	if (OidIsValid(oprForm->oprcode))
+		LockNotPinnedObject(ProcedureRelationId, oprForm->oprcode);
+
+	if (OidIsValid(oprForm->oprrest))
+		LockNotPinnedObject(ProcedureRelationId, oprForm->oprrest);
+
+	if (OidIsValid(oprForm->oprjoin))
+		LockNotPinnedObject(ProcedureRelationId, oprForm->oprjoin);
+
 	address = makeOperatorDependencies(tup, false, true);
 
 	if (OidIsValid(commutatorOid) || OidIsValid(negatorOid))
diff --git a/src/backend/commands/policy.c b/src/backend/commands/policy.c
index 6ff3eba824..31e61e4e67 100644
--- a/src/backend/commands/policy.c
+++ b/src/backend/commands/policy.c
@@ -722,6 +722,7 @@ CreatePolicy(CreatePolicyStmt *stmt)
 	myself.objectId = policy_id;
 	myself.objectSubId = 0;
 
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
 
 	recordDependencyOnExpr(&myself, qual, qual_pstate->p_rtable,
@@ -1053,6 +1054,7 @@ AlterPolicy(AlterPolicyStmt *stmt)
 	myself.objectId = policy_id;
 	myself.objectSubId = 0;
 
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&myself, &target, DEPENDENCY_AUTO);
 
 	recordDependencyOnExpr(&myself, qual, qual_parse_rtable, DEPENDENCY_NORMAL);
diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c
index 881f90017e..fadfd9064f 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -190,12 +190,14 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 	/* dependency on the PL handler function */
 	ObjectAddressSet(referenced, ProcedureRelationId, handlerOid);
 	add_exact_object_address(&referenced, addrs);
+	LockNotPinnedObject(ProcedureRelationId, handlerOid);
 
 	/* dependency on the inline handler function, if any */
 	if (OidIsValid(inlineOid))
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, inlineOid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, inlineOid);
 	}
 
 	/* dependency on the validator function, if any */
@@ -203,6 +205,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
 	{
 		ObjectAddressSet(referenced, ProcedureRelationId, valOid);
 		add_exact_object_address(&referenced, addrs);
+		LockNotPinnedObject(ProcedureRelationId, valOid);
 	}
 
 	record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 28f8522264..b1db4e8933 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -1681,6 +1681,7 @@ process_owned_by(Relation seqrel, List *owned_by, bool for_identity)
 		depobject.classId = RelationRelationId;
 		depobject.objectId = RelationGetRelid(seqrel);
 		depobject.objectSubId = 0;
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&depobject, &refobject, deptype);
 	}
 
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index 1db3ef69d2..372ae02650 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -536,6 +536,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	for (i = 0; i < nattnums; i++)
 	{
 		ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
 	}
 
@@ -553,6 +554,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	if (!nattnums)
 	{
 		ObjectAddressSet(parentobject, RelationRelationId, relid);
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
 	}
 
@@ -573,6 +575,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	 * than the underlying table(s).
 	 */
 	ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
+	LockNotPinnedObject(NamespaceRelationId, namespaceId);
 	recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
 
 	recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 66cda26a25..c66f110984 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -3438,6 +3438,7 @@ StoreCatalogInheritance1(Oid relationId, Oid parentOid,
 	childobject.objectId = relationId;
 	childobject.objectSubId = 0;
 
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&childobject, &parentobject,
 					   child_dependency_type(child_is_partition));
 
@@ -7349,7 +7350,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	/*
 	 * Add needed dependency entries for the new column.
 	 */
+	LockNotPinnedObject(TypeRelationId, attribute->atttypid);
 	add_column_datatype_dependency(myrelid, newattnum, attribute->atttypid);
+	LockNotPinnedObject(CollationRelationId, attribute->attcollation);
 	add_column_collation_dependency(myrelid, newattnum, attribute->attcollation);
 
 	/*
@@ -10174,6 +10177,7 @@ addFkRecurseReferenced(List **wqueue, Constraint *fkconstraint, Relation rel,
 		ObjectAddress referenced;
 
 		ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
+		LockNotPinnedObject(ConstraintRelationId, parentConstr);
 		recordDependencyOn(&address, &referenced, DEPENDENCY_INTERNAL);
 	}
 
@@ -10465,8 +10469,10 @@ addFkRecurseReferencing(List **wqueue, Constraint *fkconstraint, Relation rel,
 			 */
 			ObjectAddressSet(address, ConstraintRelationId, constrOid);
 			ObjectAddressSet(referenced, ConstraintRelationId, parentConstr);
+			LockNotPinnedObject(ConstraintRelationId, parentConstr);
 			recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
 			ObjectAddressSet(referenced, RelationRelationId, partitionId);
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
 
 			/* Make all this visible before recursing */
@@ -10967,9 +10973,11 @@ CloneFkReferencing(List **wqueue, Relation parentRel, Relation partRel)
 		/* Set up partition dependencies for the new constraint */
 		ObjectAddressSet(address, ConstraintRelationId, constrOid);
 		ObjectAddressSet(referenced, ConstraintRelationId, parentConstrOid);
+		LockDatabaseObject(ConstraintRelationId, parentConstrOid, 0, AccessShareLock);
 		recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_PRI);
 		ObjectAddressSet(referenced, RelationRelationId,
 						 RelationGetRelid(partRel));
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&address, &referenced, DEPENDENCY_PARTITION_SEC);
 
 		/* Done with the cloned constraint's tuple */
@@ -13254,7 +13262,9 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 	table_close(attrelation, RowExclusiveLock);
 
 	/* Install dependencies on new datatype and collation */
+	LockNotPinnedObject(TypeRelationId, targettype);
 	add_column_datatype_dependency(RelationGetRelid(rel), attnum, targettype);
+	LockNotPinnedObject(CollationRelationId, targetcollid);
 	add_column_collation_dependency(RelationGetRelid(rel), attnum, targetcollid);
 
 	/*
@@ -14816,6 +14826,7 @@ ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
 		 */
 		ObjectAddressSet(relobj, RelationRelationId, reloid);
 		ObjectAddressSet(referenced, AccessMethodRelationId, rd_rel->relam);
+		LockNotPinnedObject(AccessMethodRelationId, rd_rel->relam);
 		recordDependencyOn(&relobj, &referenced, DEPENDENCY_NORMAL);
 	}
 	else if (OidIsValid(oldAccessMethodId) &&
@@ -14835,6 +14846,7 @@ ATExecSetAccessMethodNoStorage(Relation rel, Oid newAccessMethodId)
 			   OidIsValid(rd_rel->relam));
 
 		/* Both are valid, so update the dependency */
+		LockNotPinnedObject(AccessMethodRelationId, rd_rel->relam);
 		changeDependencyFor(RelationRelationId, reloid,
 							AccessMethodRelationId,
 							oldAccessMethodId, rd_rel->relam);
@@ -16434,6 +16446,7 @@ ATExecAddOf(Relation rel, const TypeName *ofTypename, LOCKMODE lockmode)
 	typeobj.classId = TypeRelationId;
 	typeobj.objectId = typeid;
 	typeobj.objectSubId = 0;
+	LockNotPinnedObject(TypeRelationId, typeid);
 	recordDependencyOn(&tableobj, &typeobj, DEPENDENCY_NORMAL);
 
 	/* Update pg_class.reloftype */
@@ -17192,14 +17205,17 @@ AlterRelationNamespaceInternal(Relation classRel, Oid relOid,
 		CatalogTupleUpdate(classRel, &classTup->t_self, classTup);
 
 		/* Update dependency on schema if caller said so */
-		if (hasDependEntry &&
-			changeDependencyFor(RelationRelationId,
-								relOid,
-								NamespaceRelationId,
-								oldNspOid,
-								newNspOid) != 1)
-			elog(ERROR, "could not change schema dependency for relation \"%s\"",
-				 NameStr(classForm->relname));
+		if (hasDependEntry)
+		{
+			LockNotPinnedObject(NamespaceRelationId, newNspOid);
+			if (changeDependencyFor(RelationRelationId,
+									relOid,
+									NamespaceRelationId,
+									oldNspOid,
+									newNspOid) != 1)
+				elog(ERROR, "could not change schema dependency for relation \"%s\"",
+					 NameStr(classForm->relname));
+		}
 	}
 	if (!already_done)
 	{
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index 95de402fa6..44b15349b3 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -1018,8 +1018,6 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 		((Form_pg_class) GETSTRUCT(tuple))->relhastriggers = true;
 
 		CatalogTupleUpdate(pgrel, &tuple->t_self, tuple);
-
-		CommandCounterIncrement();
 	}
 	else
 		CacheInvalidateRelcacheByTuple(tuple);
@@ -1027,6 +1025,12 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 	heap_freetuple(tuple);
 	table_close(pgrel, RowExclusiveLock);
 
+	/*
+	 * CommandCounterIncrement here to ensure the new trigger entry is visible
+	 * when we'll check of object existence when recording the dependencies.
+	 */
+	CommandCounterIncrement();
+
 	/*
 	 * If we're replacing a trigger, flush all the old dependencies before
 	 * recording new ones.
@@ -1045,6 +1049,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 	referenced.classId = ProcedureRelationId;
 	referenced.objectId = funcoid;
 	referenced.objectSubId = 0;
+	LockNotPinnedObject(ProcedureRelationId, funcoid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 
 	if (isInternal && OidIsValid(constraintOid))
@@ -1058,6 +1063,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 		referenced.classId = ConstraintRelationId;
 		referenced.objectId = constraintOid;
 		referenced.objectSubId = 0;
+		LockNotPinnedObject(ConstraintRelationId, constraintOid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
 	}
 	else
@@ -1070,6 +1076,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 		referenced.classId = RelationRelationId;
 		referenced.objectId = RelationGetRelid(rel);
 		referenced.objectSubId = 0;
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 
 		if (OidIsValid(constrrelid))
@@ -1077,6 +1084,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 			referenced.classId = RelationRelationId;
 			referenced.objectId = constrrelid;
 			referenced.objectSubId = 0;
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_AUTO);
 		}
 		/* Not possible to have an index dependency in this case */
@@ -1091,6 +1099,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 			referenced.classId = ConstraintRelationId;
 			referenced.objectId = constraintOid;
 			referenced.objectSubId = 0;
+			LockNotPinnedObject(TriggerRelationId, trigoid);
 			recordDependencyOn(&referenced, &myself, DEPENDENCY_INTERNAL);
 		}
 
@@ -1100,8 +1109,10 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 		if (OidIsValid(parentTriggerOid))
 		{
 			ObjectAddressSet(referenced, TriggerRelationId, parentTriggerOid);
+			LockNotPinnedObject(TriggerRelationId, parentTriggerOid);
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_PRI);
 			ObjectAddressSet(referenced, RelationRelationId, RelationGetRelid(rel));
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_PARTITION_SEC);
 		}
 	}
@@ -1116,6 +1127,7 @@ CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
 		for (i = 0; i < ncolumns; i++)
 		{
 			referenced.objectSubId = columns[i];
+			/* XXX Do we need a lock for RelationRelationId? */
 			recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
 		}
 	}
@@ -1255,9 +1267,11 @@ TriggerSetParentTrigger(Relation trigRel,
 		ObjectAddressSet(depender, TriggerRelationId, childTrigId);
 
 		ObjectAddressSet(referenced, TriggerRelationId, parentTrigId);
+		LockNotPinnedObject(TriggerRelationId, parentTrigId);
 		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_PRI);
 
 		ObjectAddressSet(referenced, RelationRelationId, childTableId);
+		/* XXX Do we need a lock for RelationRelationId? */
 		recordDependencyOn(&depender, &referenced, DEPENDENCY_PARTITION_SEC);
 	}
 	else
diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c
index b7b5019f1e..29e02f4946 100644
--- a/src/backend/commands/tsearchcmds.c
+++ b/src/backend/commands/tsearchcmds.c
@@ -66,12 +66,12 @@ static DefElem *buildDefItem(const char *name, const char *val,
 /* --------------------- TS Parser commands ------------------------ */
 
 /*
- * lookup a parser support function and return its OID (as a Datum)
+ * lookup a parser support function and return its OID
  *
  * attnum is the pg_ts_parser column the function will go into
  */
-static Datum
-get_ts_parser_func(DefElem *defel, int attnum)
+static Oid
+get_ts_parser_func_oid(DefElem *defel, int attnum)
 {
 	List	   *funcName = defGetQualifiedName(defel);
 	Oid			typeId[3];
@@ -125,7 +125,7 @@ get_ts_parser_func(DefElem *defel, int attnum)
 						func_signature_string(funcName, nargs, NIL, typeId),
 						format_type_be(retTypeId))));
 
-	return ObjectIdGetDatum(procOid);
+	return procOid;
 }
 
 /*
@@ -214,6 +214,7 @@ DefineTSParser(List *names, List *parameters)
 	namestrcpy(&pname, prsname);
 	values[Anum_pg_ts_parser_prsname - 1] = NameGetDatum(&pname);
 	values[Anum_pg_ts_parser_prsnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
 
 	/*
 	 * loop over the definition list and extract the information we need.
@@ -224,28 +225,38 @@ DefineTSParser(List *names, List *parameters)
 
 		if (strcmp(defel->defname, "start") == 0)
 		{
-			values[Anum_pg_ts_parser_prsstart - 1] =
-				get_ts_parser_func(defel, Anum_pg_ts_parser_prsstart);
+			Oid			procoid = get_ts_parser_func_oid(defel, Anum_pg_ts_parser_prsstart);
+
+			values[Anum_pg_ts_parser_prsstart - 1] = ObjectIdGetDatum(procoid);
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else if (strcmp(defel->defname, "gettoken") == 0)
 		{
-			values[Anum_pg_ts_parser_prstoken - 1] =
-				get_ts_parser_func(defel, Anum_pg_ts_parser_prstoken);
+			Oid			procoid = get_ts_parser_func_oid(defel, Anum_pg_ts_parser_prstoken);
+
+			values[Anum_pg_ts_parser_prstoken - 1] = ObjectIdGetDatum(procoid);
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else if (strcmp(defel->defname, "end") == 0)
 		{
-			values[Anum_pg_ts_parser_prsend - 1] =
-				get_ts_parser_func(defel, Anum_pg_ts_parser_prsend);
+			Oid			procoid = get_ts_parser_func_oid(defel, Anum_pg_ts_parser_prsend);
+
+			values[Anum_pg_ts_parser_prsend - 1] = ObjectIdGetDatum(procoid);
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else if (strcmp(defel->defname, "headline") == 0)
 		{
-			values[Anum_pg_ts_parser_prsheadline - 1] =
-				get_ts_parser_func(defel, Anum_pg_ts_parser_prsheadline);
+			Oid			procoid = get_ts_parser_func_oid(defel, Anum_pg_ts_parser_prsheadline);
+
+			values[Anum_pg_ts_parser_prsheadline - 1] = ObjectIdGetDatum(procoid);
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else if (strcmp(defel->defname, "lextypes") == 0)
 		{
-			values[Anum_pg_ts_parser_prslextype - 1] =
-				get_ts_parser_func(defel, Anum_pg_ts_parser_prslextype);
+			Oid			procoid = get_ts_parser_func_oid(defel, Anum_pg_ts_parser_prslextype);
+
+			values[Anum_pg_ts_parser_prslextype - 1] = ObjectIdGetDatum(procoid);
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else
 			ereport(ERROR,
@@ -474,6 +485,10 @@ DefineTSDictionary(List *names, List *parameters)
 
 	CatalogTupleInsert(dictRel, tup);
 
+	/* Lock objects */
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
+	LockNotPinnedObject(TSTemplateRelationId, templId);
+
 	address = makeDictionaryDependencies(tup);
 
 	/* Post creation hook for new text search dictionary */
@@ -601,12 +616,12 @@ AlterTSDictionary(AlterTSDictionaryStmt *stmt)
 /* ---------------------- TS Template commands -----------------------*/
 
 /*
- * lookup a template support function and return its OID (as a Datum)
+ * lookup a template support function and return its OID
  *
  * attnum is the pg_ts_template column the function will go into
  */
-static Datum
-get_ts_template_func(DefElem *defel, int attnum)
+static Oid
+get_ts_template_func_oid(DefElem *defel, int attnum)
 {
 	List	   *funcName = defGetQualifiedName(defel);
 	Oid			typeId[4];
@@ -642,7 +657,7 @@ get_ts_template_func(DefElem *defel, int attnum)
 						func_signature_string(funcName, nargs, NIL, typeId),
 						format_type_be(retTypeId))));
 
-	return ObjectIdGetDatum(procOid);
+	return procOid;
 }
 
 /*
@@ -723,6 +738,7 @@ DefineTSTemplate(List *names, List *parameters)
 	namestrcpy(&dname, tmplname);
 	values[Anum_pg_ts_template_tmplname - 1] = NameGetDatum(&dname);
 	values[Anum_pg_ts_template_tmplnamespace - 1] = ObjectIdGetDatum(namespaceoid);
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
 
 	/*
 	 * loop over the definition list and extract the information we need.
@@ -733,15 +749,19 @@ DefineTSTemplate(List *names, List *parameters)
 
 		if (strcmp(defel->defname, "init") == 0)
 		{
-			values[Anum_pg_ts_template_tmplinit - 1] =
-				get_ts_template_func(defel, Anum_pg_ts_template_tmplinit);
+			Oid			procoid = get_ts_template_func_oid(defel, Anum_pg_ts_template_tmplinit);
+
+			values[Anum_pg_ts_template_tmplinit - 1] = ObjectIdGetDatum(procoid);
 			nulls[Anum_pg_ts_template_tmplinit - 1] = false;
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else if (strcmp(defel->defname, "lexize") == 0)
 		{
-			values[Anum_pg_ts_template_tmpllexize - 1] =
-				get_ts_template_func(defel, Anum_pg_ts_template_tmpllexize);
+			Oid			procoid = get_ts_template_func_oid(defel, Anum_pg_ts_template_tmpllexize);
+
+			values[Anum_pg_ts_template_tmpllexize - 1] = ObjectIdGetDatum(procoid);
 			nulls[Anum_pg_ts_template_tmpllexize - 1] = false;
+			LockNotPinnedObject(ProcedureRelationId, procoid);
 		}
 		else
 			ereport(ERROR,
@@ -879,6 +899,7 @@ makeConfigurationDependencies(HeapTuple tuple, bool removeOld,
 			referenced.objectId = cfgmap->mapdict;
 			referenced.objectSubId = 0;
 			add_exact_object_address(&referenced, addrs);
+			LockNotPinnedObject(TSDictionaryRelationId, cfgmap->mapdict);
 		}
 
 		systable_endscan(scan);
@@ -998,6 +1019,10 @@ DefineTSConfiguration(List *names, List *parameters, ObjectAddress *copied)
 	values[Anum_pg_ts_config_cfgowner - 1] = ObjectIdGetDatum(GetUserId());
 	values[Anum_pg_ts_config_cfgparser - 1] = ObjectIdGetDatum(prsOid);
 
+	/* Lock objects */
+	LockNotPinnedObject(NamespaceRelationId, namespaceoid);
+	LockNotPinnedObject(TSParserRelationId, prsOid);
+
 	tup = heap_form_tuple(cfgRel->rd_att, values, nulls);
 
 	CatalogTupleInsert(cfgRel, tup);
@@ -1156,6 +1181,7 @@ ObjectAddress
 AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 {
 	HeapTuple	tup;
+	Form_pg_ts_config cfg;
 	Oid			cfgId;
 	Relation	relMap;
 	ObjectAddress address;
@@ -1168,7 +1194,8 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 				 errmsg("text search configuration \"%s\" does not exist",
 						NameListToString(stmt->cfgname))));
 
-	cfgId = ((Form_pg_ts_config) GETSTRUCT(tup))->oid;
+	cfg = (Form_pg_ts_config) GETSTRUCT(tup);
+	cfgId = cfg->oid;
 
 	/* must be owner */
 	if (!object_ownercheck(TSConfigRelationId, cfgId, GetUserId()))
@@ -1183,6 +1210,10 @@ AlterTSConfiguration(AlterTSConfigurationStmt *stmt)
 	else if (stmt->tokentype)
 		DropConfigurationMapping(stmt, tup, relMap);
 
+	/* Lock dependent objects */
+	LockNotPinnedObject(NamespaceRelationId, cfg->cfgnamespace);
+	LockNotPinnedObject(TSParserRelationId, cfg->cfgparser);
+
 	/* Update dependencies */
 	makeConfigurationDependencies(tup, true, relMap);
 
@@ -1414,6 +1445,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 				repl_val[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictNew);
 				repl_repl[Anum_pg_ts_config_map_mapdict - 1] = true;
 
+				LockNotPinnedObject(TSDictionaryRelationId, dictNew);
+
 				newtup = heap_modify_tuple(maptup,
 										   RelationGetDescr(relMap),
 										   repl_val, repl_null, repl_repl);
@@ -1456,6 +1489,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt,
 				slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapseqno - 1] = Int32GetDatum(j + 1);
 				slot[slotCount]->tts_values[Anum_pg_ts_config_map_mapdict - 1] = ObjectIdGetDatum(dictIds[j]);
 
+				LockNotPinnedObject(TSDictionaryRelationId, dictIds[j]);
+
 				ExecStoreVirtualTuple(slot[slotCount]);
 				slotCount++;
 
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 2a1e713335..9febaa24a7 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -1794,6 +1794,7 @@ makeRangeConstructors(const char *name, Oid namespace,
 		 * that they go away silently when the type is dropped.  Note that
 		 * pg_dump depends on this choice to avoid dumping the constructors.
 		 */
+		LockNotPinnedObject(TypeRelationId, rangeOid);
 		recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
 	}
 }
@@ -1859,6 +1860,7 @@ makeMultirangeConstructors(const char *name, Oid namespace,
 	 * that they go away silently when the type is dropped.  Note that pg_dump
 	 * depends on this choice to avoid dumping the constructors.
 	 */
+	LockNotPinnedObject(TypeRelationId, multirangeOid);
 	recordDependencyOn(&myself, &referenced, DEPENDENCY_INTERNAL);
 	pfree(argtypes);
 
@@ -2672,6 +2674,45 @@ AlterDomainDefault(List *names, Node *defaultRaw)
 
 	CatalogTupleUpdate(rel, &tup->t_self, newtuple);
 
+	/* Lock dependent objects */
+	typTup = (Form_pg_type) GETSTRUCT(newtuple);
+
+	if (OidIsValid(typTup->typnamespace))
+		LockNotPinnedObject(NamespaceRelationId, typTup->typnamespace);
+
+	if (OidIsValid(typTup->typinput))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typinput);
+
+	if (OidIsValid(typTup->typoutput))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typoutput);
+
+	if (OidIsValid(typTup->typreceive))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typreceive);
+
+	if (OidIsValid(typTup->typsend))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typsend);
+
+	if (OidIsValid(typTup->typmodin))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typmodin);
+
+	if (OidIsValid(typTup->typmodout))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typmodout);
+
+	if (OidIsValid(typTup->typanalyze))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typanalyze);
+
+	if (OidIsValid(typTup->typsubscript))
+		LockNotPinnedObject(ProcedureRelationId, typTup->typsubscript);
+
+	if (OidIsValid(typTup->typbasetype))
+		LockNotPinnedObject(TypeRelationId, typTup->typbasetype);
+
+	if (OidIsValid(typTup->typcollation))
+		LockNotPinnedObject(CollationRelationId, typTup->typcollation);
+
+	if (OidIsValid(typTup->typelem))
+		LockNotPinnedObject(TypeRelationId, typTup->typelem);
+
 	/* Rebuild dependencies */
 	GenerateTypeDependencies(newtuple,
 							 rel,
@@ -4276,10 +4317,13 @@ AlterTypeNamespaceInternal(Oid typeOid, Oid nspOid,
 	if (oldNspOid != nspOid &&
 		(isCompositeType || typform->typtype != TYPTYPE_COMPOSITE) &&
 		!isImplicitArray)
+	{
+		LockNotPinnedObject(NamespaceRelationId, nspOid);
 		if (changeDependencyFor(TypeRelationId, typeOid,
 								NamespaceRelationId, oldNspOid, nspOid) != 1)
 			elog(ERROR, "could not change schema dependency for type \"%s\"",
 				 format_type_be(typeOid));
+	}
 
 	InvokeObjectPostAlterHook(TypeRelationId, typeOid, 0);
 
@@ -4571,6 +4615,7 @@ AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
 	SysScanDesc scan;
 	ScanKeyData key[1];
 	HeapTuple	domainTup;
+	Form_pg_type typeForm;
 
 	/* Since this function recurses, it could be driven to stack overflow */
 	check_stack_depth();
@@ -4619,6 +4664,45 @@ AlterTypeRecurse(Oid typeOid, bool isImplicitArray,
 	newtup = heap_modify_tuple(tup, RelationGetDescr(catalog),
 							   values, nulls, replaces);
 
+	/* Lock dependent objects */
+	typeForm = (Form_pg_type) GETSTRUCT(newtup);
+
+	if (OidIsValid(typeForm->typnamespace))
+		LockNotPinnedObject(NamespaceRelationId, typeForm->typnamespace);
+
+	if (OidIsValid(typeForm->typinput))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typinput);
+
+	if (OidIsValid(typeForm->typoutput))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typoutput);
+
+	if (OidIsValid(typeForm->typreceive))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typreceive);
+
+	if (OidIsValid(typeForm->typsend))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typsend);
+
+	if (OidIsValid(typeForm->typmodin))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typmodin);
+
+	if (OidIsValid(typeForm->typmodout))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typmodout);
+
+	if (OidIsValid(typeForm->typanalyze))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typanalyze);
+
+	if (OidIsValid(typeForm->typsubscript))
+		LockNotPinnedObject(ProcedureRelationId, typeForm->typsubscript);
+
+	if (OidIsValid(typeForm->typbasetype))
+		LockNotPinnedObject(TypeRelationId, typeForm->typbasetype);
+
+	if (OidIsValid(typeForm->typcollation))
+		LockNotPinnedObject(CollationRelationId, typeForm->typcollation);
+
+	if (OidIsValid(typeForm->typelem))
+		LockNotPinnedObject(TypeRelationId, typeForm->typelem);
+
 	CatalogTupleUpdate(catalog, &newtup->t_self, newtup);
 
 	/* Rebuild dependencies for this type */
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6cc9a8d8bf..ccd03ceeb8 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -155,6 +155,7 @@ InsertRule(const char *rulname,
 	referenced.objectId = eventrel_oid;
 	referenced.objectSubId = 0;
 
+	/* XXX Do we need a lock for RelationRelationId? */
 	recordDependencyOn(&myself, &referenced,
 					   (evtype == CMD_SELECT) ? DEPENDENCY_INTERNAL : DEPENDENCY_AUTO);
 
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 3250d539e1..60e8539fe3 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -271,6 +271,7 @@ Section: Class 28 - Invalid Authorization Specification
 Section: Class 2B - Dependent Privilege Descriptors Still Exist
 
 2B000    E    ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST            dependent_privilege_descriptors_still_exist
+2BP02    E    ERRCODE_DEPENDENT_OBJECTS_DOES_NOT_EXIST                       dependent_objects_does_not_exist
 2BP01    E    ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST                          dependent_objects_still_exist
 
 Section: Class 2D - Invalid Transaction Termination
diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h
index 7eee66f810..c57204cc40 100644
--- a/src/include/catalog/dependency.h
+++ b/src/include/catalog/dependency.h
@@ -101,6 +101,9 @@ typedef struct ObjectAddresses ObjectAddresses;
 /* in dependency.c */
 
 extern void AcquireDeletionLock(const ObjectAddress *object, int flags);
+extern void LockNotPinnedObjectById(const ObjectAddress *object);
+extern void LockNotPinnedObjectsById(const ObjectAddress *object, int nobject);
+extern void LockNotPinnedObject(Oid classid, Oid objid);
 
 extern void ReleaseDeletionLock(const ObjectAddress *object);
 
@@ -128,6 +131,9 @@ extern void add_exact_object_address(const ObjectAddress *object,
 extern bool object_address_present(const ObjectAddress *object,
 								   const ObjectAddresses *addrs);
 
+extern void lock_record_object_address_dependencies(const ObjectAddress *depender,
+													ObjectAddresses *referenced,
+													DependencyType behavior);
 extern void record_object_address_dependencies(const ObjectAddress *depender,
 											   ObjectAddresses *referenced,
 											   DependencyType behavior);
@@ -172,6 +178,7 @@ extern long changeDependenciesOf(Oid classId, Oid oldObjectId,
 extern long changeDependenciesOn(Oid refClassId, Oid oldRefObjectId,
 								 Oid newRefObjectId);
 
+extern bool isObjectPinned(const ObjectAddress *object);
 extern Oid	getExtensionOfObject(Oid classId, Oid objectId);
 extern List *getAutoExtensionsOfObject(Oid classId, Oid objectId);
 
diff --git a/src/include/catalog/objectaddress.h b/src/include/catalog/objectaddress.h
index 3a70d80e32..56f746264b 100644
--- a/src/include/catalog/objectaddress.h
+++ b/src/include/catalog/objectaddress.h
@@ -53,6 +53,7 @@ extern void check_object_ownership(Oid roleid,
 								   Node *object, Relation relation);
 
 extern Oid	get_object_namespace(const ObjectAddress *address);
+extern bool ObjectByIdExist(const ObjectAddress *address);
 
 extern bool is_objectclass_supported(Oid class_id);
 extern const char *get_object_class_descr(Oid class_id);
diff --git a/src/test/isolation/expected/test_dependencies_locks.out b/src/test/isolation/expected/test_dependencies_locks.out
new file mode 100644
index 0000000000..9b645d7aa5
--- /dev/null
+++ b/src/test/isolation/expected/test_dependencies_locks.out
@@ -0,0 +1,129 @@
+Parsed test spec with 2 sessions
+
+starting permutation: s1_begin s1_create_function_in_schema s2_drop_schema s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_schema: DROP SCHEMA testschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_schema: <... completed>
+ERROR:  cannot drop schema testschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_schema s1_create_function_in_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_schema: DROP SCHEMA testschema;
+step s1_create_function_in_schema: CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_in_schema: <... completed>
+ERROR:  schema testschema does not exist
+
+starting permutation: s1_begin s1_alter_function_schema s2_drop_alterschema s1_commit
+step s1_begin: BEGIN;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema;
+step s2_drop_alterschema: DROP SCHEMA alterschema; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_alterschema: <... completed>
+ERROR:  cannot drop schema alterschema because other objects depend on it
+
+starting permutation: s2_begin s2_drop_alterschema s1_alter_function_schema s2_commit
+step s2_begin: BEGIN;
+step s2_drop_alterschema: DROP SCHEMA alterschema;
+step s1_alter_function_schema: ALTER FUNCTION public.falter() SET SCHEMA alterschema; <waiting ...>
+step s2_commit: COMMIT;
+step s1_alter_function_schema: <... completed>
+ERROR:  schema alterschema does not exist
+
+starting permutation: s1_begin s1_create_function_with_argtype s2_drop_foo_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql;
+step s2_drop_foo_type: DROP TYPE public.foo; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_type: <... completed>
+ERROR:  cannot drop type foo because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_type s1_create_function_with_argtype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_type: DROP TYPE public.foo;
+step s1_create_function_with_argtype: CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_argtype: <... completed>
+ERROR:  type foo does not exist
+
+starting permutation: s1_begin s1_create_function_with_rettype s2_drop_foo_rettype s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1;
+step s2_drop_foo_rettype: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_foo_rettype: <... completed>
+ERROR:  cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_foo_rettype s1_create_function_with_rettype s2_commit
+step s2_begin: BEGIN;
+step s2_drop_foo_rettype: DROP DOMAIN id;
+step s1_create_function_with_rettype: CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_rettype: <... completed>
+ERROR:  type id does not exist
+
+starting permutation: s1_begin s1_create_function_with_function s2_drop_function_f s1_commit
+step s1_begin: BEGIN;
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1;
+step s2_drop_function_f: DROP FUNCTION f(); <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_function_f: <... completed>
+ERROR:  cannot drop function f() because other objects depend on it
+
+starting permutation: s2_begin s2_drop_function_f s1_create_function_with_function s2_commit
+step s2_begin: BEGIN;
+step s2_drop_function_f: DROP FUNCTION f();
+step s1_create_function_with_function: CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_function_with_function: <... completed>
+ERROR:  function f() does not exist
+
+starting permutation: s1_begin s1_create_domain_with_domain s2_drop_domain_id s1_commit
+step s1_begin: BEGIN;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id;
+step s2_drop_domain_id: DROP DOMAIN id; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_domain_id: <... completed>
+ERROR:  cannot drop type id because other objects depend on it
+
+starting permutation: s2_begin s2_drop_domain_id s1_create_domain_with_domain s2_commit
+step s2_begin: BEGIN;
+step s2_drop_domain_id: DROP DOMAIN id;
+step s1_create_domain_with_domain: CREATE DOMAIN idid as id; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_domain_with_domain: <... completed>
+ERROR:  type id does not exist
+
+starting permutation: s1_begin s1_create_table_with_type s2_drop_footab_type s1_commit
+step s1_begin: BEGIN;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab);
+step s2_drop_footab_type: DROP TYPE public.footab; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_footab_type: <... completed>
+ERROR:  cannot drop type footab because other objects depend on it
+
+starting permutation: s2_begin s2_drop_footab_type s1_create_table_with_type s2_commit
+step s2_begin: BEGIN;
+step s2_drop_footab_type: DROP TYPE public.footab;
+step s1_create_table_with_type: CREATE TABLE tabtype(a footab); <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_table_with_type: <... completed>
+ERROR:  type footab does not exist
+
+starting permutation: s1_begin s1_create_server_with_fdw_wrapper s2_drop_fdw_wrapper s1_commit
+step s1_begin: BEGIN;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; <waiting ...>
+step s1_commit: COMMIT;
+step s2_drop_fdw_wrapper: <... completed>
+ERROR:  cannot drop foreign-data wrapper fdw_wrapper because other objects depend on it
+
+starting permutation: s2_begin s2_drop_fdw_wrapper s1_create_server_with_fdw_wrapper s2_commit
+step s2_begin: BEGIN;
+step s2_drop_fdw_wrapper: DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT;
+step s1_create_server_with_fdw_wrapper: CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; <waiting ...>
+step s2_commit: COMMIT;
+step s1_create_server_with_fdw_wrapper: <... completed>
+ERROR:  foreign-data wrapper fdw_wrapper does not exist
diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule
index 0342eb39e4..1b67f0bffe 100644
--- a/src/test/isolation/isolation_schedule
+++ b/src/test/isolation/isolation_schedule
@@ -114,3 +114,4 @@ test: serializable-parallel-2
 test: serializable-parallel-3
 test: matview-write-skew
 test: lock-nowait
+test: test_dependencies_locks
diff --git a/src/test/isolation/specs/test_dependencies_locks.spec b/src/test/isolation/specs/test_dependencies_locks.spec
new file mode 100644
index 0000000000..5d04dfe9dc
--- /dev/null
+++ b/src/test/isolation/specs/test_dependencies_locks.spec
@@ -0,0 +1,89 @@
+setup
+{
+  CREATE SCHEMA testschema;
+  CREATE SCHEMA alterschema;
+  CREATE TYPE public.foo as enum ('one', 'two');
+  CREATE TYPE public.footab as enum ('three', 'four');
+  CREATE DOMAIN id AS int;
+  CREATE FUNCTION f() RETURNS int LANGUAGE SQL RETURN 1;
+  CREATE FUNCTION public.falter() RETURNS int LANGUAGE SQL RETURN 1;
+  CREATE FOREIGN DATA WRAPPER fdw_wrapper;
+}
+
+teardown
+{
+  DROP FUNCTION IF EXISTS testschema.foo();
+  DROP FUNCTION IF EXISTS fooargtype(num foo);
+  DROP FUNCTION IF EXISTS footrettype();
+  DROP FUNCTION IF EXISTS foofunc();
+  DROP FUNCTION IF EXISTS public.falter();
+  DROP FUNCTION IF EXISTS alterschema.falter();
+  DROP DOMAIN IF EXISTS idid;
+  DROP SERVER IF EXISTS srv_fdw_wrapper;
+  DROP TABLE IF EXISTS tabtype;
+  DROP SCHEMA IF EXISTS testschema;
+  DROP SCHEMA IF EXISTS alterschema;
+  DROP TYPE IF EXISTS public.foo;
+  DROP TYPE IF EXISTS public.footab;
+  DROP DOMAIN IF EXISTS id;
+  DROP FUNCTION IF EXISTS f();
+  DROP FOREIGN DATA WRAPPER IF EXISTS fdw_wrapper;
+}
+
+session "s1"
+
+step "s1_begin" { BEGIN; }
+step "s1_create_function_in_schema" { CREATE FUNCTION testschema.foo() RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_argtype" { CREATE FUNCTION fooargtype(num foo) RETURNS int AS 'select 1' LANGUAGE sql; }
+step "s1_create_function_with_rettype" { CREATE FUNCTION footrettype() RETURNS id LANGUAGE sql RETURN 1; }
+step "s1_create_function_with_function" { CREATE FUNCTION foofunc() RETURNS int LANGUAGE SQL RETURN f() + 1; }
+step "s1_alter_function_schema" { ALTER FUNCTION public.falter() SET SCHEMA alterschema; }
+step "s1_create_domain_with_domain" { CREATE DOMAIN idid as id; }
+step "s1_create_table_with_type" { CREATE TABLE tabtype(a footab); }
+step "s1_create_server_with_fdw_wrapper" { CREATE SERVER srv_fdw_wrapper FOREIGN DATA WRAPPER fdw_wrapper; }
+step "s1_commit" { COMMIT; }
+
+session "s2"
+
+step "s2_begin" { BEGIN; }
+step "s2_drop_schema" { DROP SCHEMA testschema; }
+step "s2_drop_alterschema" { DROP SCHEMA alterschema; }
+step "s2_drop_foo_type" { DROP TYPE public.foo; }
+step "s2_drop_foo_rettype" { DROP DOMAIN id; }
+step "s2_drop_footab_type" { DROP TYPE public.footab; }
+step "s2_drop_function_f" { DROP FUNCTION f(); }
+step "s2_drop_domain_id" { DROP DOMAIN id; }
+step "s2_drop_fdw_wrapper" { DROP FOREIGN DATA WRAPPER fdw_wrapper RESTRICT; }
+step "s2_commit" { COMMIT; }
+
+# function - schema
+permutation "s1_begin" "s1_create_function_in_schema" "s2_drop_schema" "s1_commit"
+permutation "s2_begin" "s2_drop_schema" "s1_create_function_in_schema" "s2_commit"
+
+# alter function - schema
+permutation "s1_begin" "s1_alter_function_schema" "s2_drop_alterschema" "s1_commit"
+permutation "s2_begin" "s2_drop_alterschema" "s1_alter_function_schema" "s2_commit"
+
+# function - argtype
+permutation "s1_begin" "s1_create_function_with_argtype" "s2_drop_foo_type" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_type" "s1_create_function_with_argtype" "s2_commit"
+
+# function - rettype
+permutation "s1_begin" "s1_create_function_with_rettype" "s2_drop_foo_rettype" "s1_commit"
+permutation "s2_begin" "s2_drop_foo_rettype" "s1_create_function_with_rettype" "s2_commit"
+
+# function - function
+permutation "s1_begin" "s1_create_function_with_function" "s2_drop_function_f" "s1_commit"
+permutation "s2_begin" "s2_drop_function_f" "s1_create_function_with_function" "s2_commit"
+
+# domain - domain
+permutation "s1_begin" "s1_create_domain_with_domain" "s2_drop_domain_id" "s1_commit"
+permutation "s2_begin" "s2_drop_domain_id" "s1_create_domain_with_domain" "s2_commit"
+
+# table - type
+permutation "s1_begin" "s1_create_table_with_type" "s2_drop_footab_type" "s1_commit"
+permutation "s2_begin" "s2_drop_footab_type" "s1_create_table_with_type" "s2_commit"
+
+# server - foreign data wrapper
+permutation "s1_begin" "s1_create_server_with_fdw_wrapper" "s2_drop_fdw_wrapper" "s1_commit"
+permutation "s2_begin" "s2_drop_fdw_wrapper" "s1_create_server_with_fdw_wrapper" "s2_commit"
-- 
2.34.1


--bmyJBR+1NCLqhmdA--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v7 2/2] fixups
@ 2026-02-09 16:51 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ 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] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <alvherre@kurilemu.de>
  0 siblings, 0 replies; 327+ messages in thread

From: Álvaro Herrera @ 2026-07-07 16:35 UTC (permalink / raw)

---
 src/backend/commands/repack.c | 54 ++++++++++++++++++++++-------------
 1 file changed, 34 insertions(+), 20 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 2879c8af574..fcc401ccdb9 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -2152,6 +2152,10 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 		/*
 		 * For USING INDEX, scan pg_index to find those with indisclustered.
+		 *
+		 * Note we don't obtain lock of any kind on the index, which means the
+		 * index or its owning table could be gone or change at any point.  We
+		 * have to be extra careful when examining catalog state for them.
 		 */
 		catalog = table_open(IndexRelationId, AccessShareLock);
 		ScanKeyInit(&entry,
@@ -2169,7 +2173,7 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 
 			index = (Form_pg_index) GETSTRUCT(tuple);
 
-			classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(index->indrelid));
+			classtup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(index->indrelid));
 			if (!HeapTupleIsValid(classtup))
 				continue;
 			classForm = (Form_pg_class) GETSTRUCT(classtup);
@@ -2178,11 +2182,11 @@ get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt)
 			if (classForm->relpersistence == RELPERSISTENCE_TEMP &&
 				!isTempOrTempToastNamespace(classForm->relnamespace))
 			{
-				ReleaseSysCache(classtup);
+				heap_freetuple(classtup);
 				continue;
 			}
 
-			ReleaseSysCache(classtup);
+			heap_freetuple(classtup);
 
 			/* noisily skip rels which the user can't process */
 			if (!repack_is_permitted_for_relation(cmd, index->indrelid,
@@ -2274,7 +2278,9 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 			if (get_rel_relkind(child_oid) != RELKIND_INDEX)
 				continue;
 
-			table_oid = IndexGetRelation(child_oid, false);
+			table_oid = IndexGetRelation(child_oid, true);
+			if (!OidIsValid(table_oid))
+				continue;
 			index_oid = child_oid;
 		}
 		else
@@ -2309,33 +2315,41 @@ get_tables_to_repack_partitioned(RepackCommand cmd, Oid relid,
 
 
 /*
- * Return whether userid has privileges to REPACK relid.  If not, this
- * function emits a WARNING.
+ * Return whether userid has privileges to execute REPACK on relid.
+ *
+ * Caller may not have a lock on the relation, so it could have been
+ * dropped concurrently.  In that case, silently return false.
+ *
+ * If the relation does exist but the user doesn't have the required
+ * privs, emit a WARNING and return false.  Otherwise, return true.
  */
 static bool
 repack_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid)
 {
 	bool		is_missing = false;
+	AclResult	result;
+	char	   *relname;
 
 	Assert(cmd == REPACK_COMMAND_CLUSTER || cmd == REPACK_COMMAND_REPACK);
 
-	if (pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing) == ACLCHECK_OK)
+	result = pg_class_aclcheck_ext(relid, userid, ACL_MAINTAIN, &is_missing);
+	if (is_missing)
+		return false;
+
+	if (result == ACLCHECK_OK)
 		return true;
 
-	/* Report a warning if the relation still exists. */
-	if (!is_missing)
+	/*
+	 * The relation can also be dropped after we tested its ACL and before we
+	 * read its relname, so be careful.
+	 */
+	relname = get_rel_name(relid);
+	if (relname != NULL)
 	{
-		char	   *relname;
-
-		relname = get_rel_name(relid);
-		if (relname != NULL)
-		{
-			ereport(WARNING,
-					errmsg("permission denied to execute %s on \"%s\", skipping it",
-						   RepackCommandAsString(cmd), relname));
-
-			pfree(relname);
-		}
+		ereport(WARNING,
+				errmsg("permission denied to execute %s on \"%s\", skipping it",
+					   RepackCommandAsString(cmd), relname));
+		pfree(relname);
 	}
 
 	return false;
-- 
2.47.3


--op6mnexl7cn72cto--





^ permalink  raw  reply  [nested|flat] 327+ messages in thread


end of thread, other threads:[~2026-07-07 16:35 UTC | newest]

Thread overview: 327+ 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 <alvherre@alvh.no-ip.org>
2023-05-16 13:42 [PATCH v10 3/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
2023-05-16 13:42 [PATCH v9 2/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <jgdr@dalibo.com>
2024-03-29 15:43 [PATCH v10] Avoid orphaned objects dependencies Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
2026-02-09 16:51 [PATCH v7 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <alvherre@kurilemu.de>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox