agora inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v3 3/3] fixups
132+ messages / 4 participants
[nested] [flat]

* [PATCH v3 3/3] fixups
@ 2018-02-28 23:20 Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ 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] 132+ messages in thread

* [PATCH v10 3/3] Dedicated memory context for hash join spill buffers
@ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

From: Jehan-Guillaume de Rorthais @ 2023-05-16 13:42 UTC (permalink / raw)

Should a hash join exceed work_mem, its hashtable is split up into
multiple batches. The number of batches is doubled each time a given
batch is determined not to fit in memory. Each batch file is
allocated with a block-sized buffer for buffering tuples and
parallel hash join has additional sharedtuplestore accessor buffers.

In some pathological cases requiring a lot of batches, often with
skewed data, bad stats, or very large datasets, users can run
out-of-memory solely from the memory overhead of all the batch
files' buffers.

Batch files were allocated in the ExecutorState memory context, making
it very hard to identify when this batch explosion was the source of an
OOM. By allocating the batch files in a dedicated memory context, it
should be easier for users to identify the cause of an OOM and work to
avoid it.

Original draft by Tomas Vondra.

Author: Tomas Vondra <[email protected]>
Author: Jehan-Guillaume de Rorthais <[email protected]>
Reviewed-by:  Melanie Plageman <[email protected]>
Discussion: https://postgr.es/m/20190421114618.z3mpgmimc3rmubi4@development
Discussion: https://postgr.es/m/20230504193006.1b5b9622%40karst#273020ff4061fc7a2fbb1ba96b281f17
---
 src/backend/executor/nodeHash.c           | 43 ++++++++++++++++-------
 src/backend/executor/nodeHashjoin.c       | 32 +++++++++++++----
 src/backend/utils/sort/sharedtuplestore.c |  8 +++++
 src/include/executor/hashjoin.h           | 30 +++++++++++-----
 src/include/executor/nodeHashjoin.h       |  2 +-
 5 files changed, 86 insertions(+), 29 deletions(-)

diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index ac3eb32d97..7571db4c1d 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -484,7 +484,7 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 	 *
 	 * The hashtable control block is just palloc'd from the executor's
 	 * per-query memory context.  Everything else should be kept inside the
-	 * subsidiary hashCxt or batchCxt.
+	 * subsidiary hashCxt, batchCxt or spillCxt.
 	 */
 	hashtable = palloc_object(HashJoinTableData);
 	hashtable->nbuckets = nbuckets;
@@ -538,6 +538,10 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 												"HashBatchContext",
 												ALLOCSET_DEFAULT_SIZES);
 
+	hashtable->spillCxt = AllocSetContextCreate(hashtable->hashCxt,
+												"HashSpillContext",
+												ALLOCSET_DEFAULT_SIZES);
+
 	/* Allocate data that will live for the life of the hashjoin */
 
 	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
@@ -570,12 +574,19 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 
 	if (nbatch > 1 && hashtable->parallel_state == NULL)
 	{
+		MemoryContext oldctx;
+
 		/*
 		 * allocate and initialize the file arrays in hashCxt (not needed for
 		 * parallel case which uses shared tuplestores instead of raw files)
 		 */
+		oldctx = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch);
 		hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch);
+
+		MemoryContextSwitchTo(oldctx);
+
 		/* The files will not be opened until needed... */
 		/* ... but make sure we have temp tablespaces established for them */
 		PrepareTempTablespaces();
@@ -913,7 +924,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 	int			oldnbatch = hashtable->nbatch;
 	int			curbatch = hashtable->curbatch;
 	int			nbatch;
-	MemoryContext oldcxt;
 	long		ninmemory;
 	long		nfreed;
 	HashMemoryChunk oldchunks;
@@ -934,13 +944,16 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 		   hashtable, nbatch, hashtable->spaceUsed);
 #endif
 
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
-
 	if (hashtable->innerBatchFile == NULL)
 	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		/* we had no file arrays before */
 		hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch);
 		hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch);
+
+		MemoryContextSwitchTo(oldcxt);
+
 		/* time to establish the temp tablespaces, too */
 		PrepareTempTablespaces();
 	}
@@ -951,8 +964,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 		hashtable->outerBatchFile = repalloc0_array(hashtable->outerBatchFile, BufFile *, oldnbatch, nbatch);
 	}
 
-	MemoryContextSwitchTo(oldcxt);
-
 	hashtable->nbatch = nbatch;
 
 	/*
@@ -1024,7 +1035,8 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 				Assert(batchno > curbatch);
 				ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(hashTuple),
 									  hashTuple->hashvalue,
-									  &hashtable->innerBatchFile[batchno]);
+									  &hashtable->innerBatchFile[batchno],
+									  hashtable);
 
 				hashtable->spaceUsed -= hashTupleSize;
 				nfreed++;
@@ -1683,7 +1695,8 @@ ExecHashTableInsert(HashJoinTable hashtable,
 		Assert(batchno > hashtable->curbatch);
 		ExecHashJoinSaveTuple(tuple,
 							  hashvalue,
-							  &hashtable->innerBatchFile[batchno]);
+							  &hashtable->innerBatchFile[batchno],
+							  hashtable);
 	}
 
 	if (shouldFree)
@@ -2664,7 +2677,8 @@ ExecHashRemoveNextSkewBucket(HashJoinTable hashtable)
 			/* Put the tuple into a temp file for later batches */
 			Assert(batchno > hashtable->curbatch);
 			ExecHashJoinSaveTuple(tuple, hashvalue,
-								  &hashtable->innerBatchFile[batchno]);
+								  &hashtable->innerBatchFile[batchno],
+								  hashtable);
 			pfree(hashTuple);
 			hashtable->spaceUsed -= tupleSize;
 			hashtable->spaceUsedSkew -= tupleSize;
@@ -3093,8 +3107,11 @@ ExecParallelHashJoinSetUpBatches(HashJoinTable hashtable, int nbatch)
 	pstate->nbatch = nbatch;
 	batches = dsa_get_address(hashtable->area, pstate->batches);
 
-	/* Use hash join memory context. */
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
+	/*
+	 * Use hash join spill memory context to allocate accessors and their
+	 * buffers.
+	 */
+	oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
 
 	/* Allocate this backend's accessor array. */
 	hashtable->nbatch = nbatch;
@@ -3196,8 +3213,8 @@ ExecParallelHashEnsureBatchAccessors(HashJoinTable hashtable)
 	 */
 	Assert(DsaPointerIsValid(pstate->batches));
 
-	/* Use hash join memory context. */
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
+	/* Use hash join spill memory context to allocate accessors. */
+	oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
 
 	/* Allocate this backend's accessor array. */
 	hashtable->nbatch = pstate->nbatch;
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 35b005a6a4..42a3c64fb9 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -489,7 +489,8 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
 					Assert(parallel_state == NULL);
 					Assert(batchno > hashtable->curbatch);
 					ExecHashJoinSaveTuple(mintuple, hashvalue,
-										  &hashtable->outerBatchFile[batchno]);
+										  &hashtable->outerBatchFile[batchno],
+										  hashtable);
 
 					if (shouldFree)
 						heap_free_minimal_tuple(mintuple);
@@ -1311,21 +1312,40 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate)
  * The data recorded in the file for each tuple is its hash value,
  * then the tuple in MinimalTuple format.
  *
- * Note: it is important always to call this in the regular executor
- * context, not in a shorter-lived context; else the temp file buffers
- * will get messed up.
+ * fileptr points to either an inner or outer batch file inside the hashtable
+ * arrays.
  */
 void
 ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
-					  BufFile **fileptr)
+					  BufFile **fileptr, HashJoinTable hashtable)
 {
 	BufFile    *file = *fileptr;
 
 	if (file == NULL)
 	{
-		/* First write to this batch file, so open it. */
+		MemoryContext oldctx;
+
+		/*
+		 * The batch file is lazily created. If this is the first tuple
+		 * written to this batch, the batch file is created and its buffer is
+		 * allocated in the spillCxt context, NOT in the batchCxt.
+		 *
+		 * During the build phase, buffered files are created for inner
+		 * batches. Each batch's buffered file is closed (and its buffer freed)
+		 * after the batch is loaded into memory during the outer side scan.
+		 * Therefore, it is necessary to allocate the batch file buffer in a
+		 * memory context which outlives the batch itself.
+		 *
+		 * Also, we use spillCxt instead of hashCxt for a better accounting of
+		 * the spilling memory consumption.
+		 */
+
+		oldctx = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		file = BufFileCreateTemp(false);
 		*fileptr = file;
+
+		MemoryContextSwitchTo(oldctx);
 	}
 
 	BufFileWrite(file, &hashvalue, sizeof(uint32));
diff --git a/src/backend/utils/sort/sharedtuplestore.c b/src/backend/utils/sort/sharedtuplestore.c
index 0831249159..236be65f22 100644
--- a/src/backend/utils/sort/sharedtuplestore.c
+++ b/src/backend/utils/sort/sharedtuplestore.c
@@ -308,11 +308,15 @@ sts_puttuple(SharedTuplestoreAccessor *accessor, void *meta_data,
 	{
 		SharedTuplestoreParticipant *participant;
 		char		name[MAXPGPATH];
+		MemoryContext oldcxt;
 
 		/* Create one.  Only this backend will write into it. */
 		sts_filename(name, accessor, accessor->participant);
+
+		oldcxt = MemoryContextSwitchTo(accessor->context);
 		accessor->write_file =
 			BufFileCreateFileSet(&accessor->fileset->fs, name);
+		MemoryContextSwitchTo(oldcxt);
 
 		/* Set up the shared state for this backend's file. */
 		participant = &accessor->sts->participants[accessor->participant];
@@ -527,11 +531,15 @@ sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data)
 			if (accessor->read_file == NULL)
 			{
 				char		name[MAXPGPATH];
+				MemoryContext oldcxt;
 
 				sts_filename(name, accessor, accessor->read_participant);
+
+				oldcxt = MemoryContextSwitchTo(accessor->context);
 				accessor->read_file =
 					BufFileOpenFileSet(&accessor->fileset->fs, name, O_RDONLY,
 									   false);
+				MemoryContextSwitchTo(oldcxt);
 			}
 
 			/* Seek and load the chunk header. */
diff --git a/src/include/executor/hashjoin.h b/src/include/executor/hashjoin.h
index 8ee59d2c71..857ca58f6f 100644
--- a/src/include/executor/hashjoin.h
+++ b/src/include/executor/hashjoin.h
@@ -23,12 +23,12 @@
 /* ----------------------------------------------------------------
  *				hash-join hash table structures
  *
- * Each active hashjoin has a HashJoinTable control block, which is
- * palloc'd in the executor's per-query context.  All other storage needed
- * for the hashjoin is kept in private memory contexts, two for each hashjoin.
- * This makes it easy and fast to release the storage when we don't need it
- * anymore.  (Exception: data associated with the temp files lives in the
- * per-query context too, since we always call buffile.c in that context.)
+ * Each active hashjoin has a HashJoinTable structure, which is
+ * palloc'd in the executor's per-query context.  Other storage needed for
+ * each hashjoin is kept in child contexts, three for each hashjoin:
+ *   - HashTableContext (hashCxt): the parent hash table storage context
+ *   - HashSpillContext (spillCxt): storage for temp files buffers
+ *   - HashBatchContext (batchCxt): storage for a batch in serial hash join
  *
  * The hashtable contexts are made children of the per-query context, ensuring
  * that they will be discarded at end of statement even if the join is
@@ -36,9 +36,20 @@
  * be cleaned up by the virtual file manager in event of an error.)
  *
  * Storage that should live through the entire join is allocated from the
- * "hashCxt", while storage that is only wanted for the current batch is
- * allocated in the "batchCxt".  By resetting the batchCxt at the end of
- * each batch, we free all the per-batch storage reliably and without tedium.
+ * "hashCxt" (mainly the hashtable's metadata). Also, the "hashCxt" context is
+ * the parent of "spillCxt" and "batchCxt". It makes it easy and fast to
+ * release the storage when we don't need it anymore.
+ *
+ * Data associated with temp files is allocated in the "spillCxt" context
+ * which lives for the duration of the entire join as batch files'
+ * creation and usage may span batch execution. These files are
+ * explicitly destroyed by calling BufFileClose() when the code is done
+ * with them. The aim of this context is to help accounting for the
+ * memory allocated for temp files and their buffers.
+ *
+ * Finally, data used only during a single batch's execution is allocated
+ * in the "batchCxt". By resetting the batchCxt at the end of each batch,
+ * we free all the per-batch storage reliably and without tedium.
  *
  * During first scan of inner relation, we get its tuples from executor.
  * If nbatch > 1 then tuples that don't belong in first batch get saved
@@ -350,6 +361,7 @@ typedef struct HashJoinTableData
 
 	MemoryContext hashCxt;		/* context for whole-hash-join storage */
 	MemoryContext batchCxt;		/* context for this-batch-only storage */
+	MemoryContext spillCxt;		/* context for spilling to temp files */
 
 	/* used for dense allocation of tuples (into linked chunks) */
 	HashMemoryChunk chunks;		/* one list for the whole batch */
diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h
index d367070883..ccb704ede1 100644
--- a/src/include/executor/nodeHashjoin.h
+++ b/src/include/executor/nodeHashjoin.h
@@ -29,6 +29,6 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state,
 										 ParallelWorkerContext *pwcxt);
 
 extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
-								  BufFile **fileptr);
+								  BufFile **fileptr, HashJoinTable hashtable);
 
 #endif							/* NODEHASHJOIN_H */
-- 
2.40.1


--MP_/zjnH27gFIT.OObnMRRKM4.=--





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

* [PATCH v9 2/3] Dedicated memory context for hash join spill buffers
@ 2023-05-16 13:42 Jehan-Guillaume de Rorthais <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

From: Jehan-Guillaume de Rorthais @ 2023-05-16 13:42 UTC (permalink / raw)

Should a hash join exceed work_mem, its hashtable is split up into
multiple batches. The number of batches is doubled each time a given
batch is determined not to fit in memory. Each batch file is
allocated with a block-sized buffer for buffering tuples and
parallel hash join has additional sharedtuplestore accessor buffers.

In some pathological cases requiring a lot of batches, often with
skewed data, bad stats, or very large datasets, users can run
out-of-memory solely from the memory overhead of all the batch
files' buffers.

Batch files were allocated in the ExecutorState memory context, making
it very hard to identify when this batch explosion was the source of an
OOM. By allocating the batch files in a dedicated memory context, it
should be easier for users to identify the cause of an OOM and work to
avoid it.

Original draft by Tomas Vondra.

Author: Tomas Vondra <[email protected]>
Author: Jehan-Guillaume de Rorthais <[email protected]>
Reviewed-by:  Melanie Plageman <[email protected]>
Discussion: https://postgr.es/m/20190421114618.z3mpgmimc3rmubi4@development
Discussion: https://postgr.es/m/20230504193006.1b5b9622%40karst#273020ff4061fc7a2fbb1ba96b281f17
---
 src/backend/executor/nodeHash.c           | 43 ++++++++++++++++-------
 src/backend/executor/nodeHashjoin.c       | 31 ++++++++++++----
 src/backend/utils/sort/sharedtuplestore.c |  8 +++++
 src/include/executor/hashjoin.h           | 30 +++++++++++-----
 src/include/executor/nodeHashjoin.h       |  2 +-
 5 files changed, 84 insertions(+), 30 deletions(-)

diff --git a/src/backend/executor/nodeHash.c b/src/backend/executor/nodeHash.c
index 5fd1c5553b..444d182bca 100644
--- a/src/backend/executor/nodeHash.c
+++ b/src/backend/executor/nodeHash.c
@@ -484,7 +484,7 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 	 *
 	 * The hashtable control block is just palloc'd from the executor's
 	 * per-query memory context.  Everything else should be kept inside the
-	 * subsidiary hashCxt or batchCxt.
+	 * subsidiary hashCxt, batchCxt or spillCxt.
 	 */
 	hashtable = palloc_object(HashJoinTableData);
 	hashtable->nbuckets = nbuckets;
@@ -538,6 +538,10 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 												"HashBatchContext",
 												ALLOCSET_DEFAULT_SIZES);
 
+	hashtable->spillCxt = AllocSetContextCreate(hashtable->hashCxt,
+												"HashSpillContext",
+												ALLOCSET_DEFAULT_SIZES);
+
 	/* Allocate data that will live for the life of the hashjoin */
 
 	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
@@ -570,12 +574,19 @@ ExecHashTableCreate(HashState *state, List *hashOperators, List *hashCollations,
 
 	if (nbatch > 1 && hashtable->parallel_state == NULL)
 	{
+		MemoryContext oldctx;
+
 		/*
 		 * allocate and initialize the file arrays in hashCxt (not needed for
 		 * parallel case which uses shared tuplestores instead of raw files)
 		 */
+		oldctx = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch);
 		hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch);
+
+		MemoryContextSwitchTo(oldctx);
+
 		/* The files will not be opened until needed... */
 		/* ... but make sure we have temp tablespaces established for them */
 		PrepareTempTablespaces();
@@ -913,7 +924,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 	int			oldnbatch = hashtable->nbatch;
 	int			curbatch = hashtable->curbatch;
 	int			nbatch;
-	MemoryContext oldcxt;
 	long		ninmemory;
 	long		nfreed;
 	HashMemoryChunk oldchunks;
@@ -934,13 +944,16 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 		   hashtable, nbatch, hashtable->spaceUsed);
 #endif
 
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
-
 	if (hashtable->innerBatchFile == NULL)
 	{
+		MemoryContext oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		/* we had no file arrays before */
 		hashtable->innerBatchFile = palloc0_array(BufFile *, nbatch);
 		hashtable->outerBatchFile = palloc0_array(BufFile *, nbatch);
+
+		MemoryContextSwitchTo(oldcxt);
+
 		/* time to establish the temp tablespaces, too */
 		PrepareTempTablespaces();
 	}
@@ -951,8 +964,6 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 		hashtable->outerBatchFile = repalloc0_array(hashtable->outerBatchFile, BufFile *, oldnbatch, nbatch);
 	}
 
-	MemoryContextSwitchTo(oldcxt);
-
 	hashtable->nbatch = nbatch;
 
 	/*
@@ -1024,7 +1035,8 @@ ExecHashIncreaseNumBatches(HashJoinTable hashtable)
 				Assert(batchno > curbatch);
 				ExecHashJoinSaveTuple(HJTUPLE_MINTUPLE(hashTuple),
 									  hashTuple->hashvalue,
-									  &hashtable->innerBatchFile[batchno]);
+									  &hashtable->innerBatchFile[batchno],
+									  hashtable);
 
 				hashtable->spaceUsed -= hashTupleSize;
 				nfreed++;
@@ -1683,7 +1695,8 @@ ExecHashTableInsert(HashJoinTable hashtable,
 		Assert(batchno > hashtable->curbatch);
 		ExecHashJoinSaveTuple(tuple,
 							  hashvalue,
-							  &hashtable->innerBatchFile[batchno]);
+							  &hashtable->innerBatchFile[batchno],
+							  hashtable);
 	}
 
 	if (shouldFree)
@@ -2664,7 +2677,8 @@ ExecHashRemoveNextSkewBucket(HashJoinTable hashtable)
 			/* Put the tuple into a temp file for later batches */
 			Assert(batchno > hashtable->curbatch);
 			ExecHashJoinSaveTuple(tuple, hashvalue,
-								  &hashtable->innerBatchFile[batchno]);
+								  &hashtable->innerBatchFile[batchno],
+								  hashtable);
 			pfree(hashTuple);
 			hashtable->spaceUsed -= tupleSize;
 			hashtable->spaceUsedSkew -= tupleSize;
@@ -3093,8 +3107,11 @@ ExecParallelHashJoinSetUpBatches(HashJoinTable hashtable, int nbatch)
 	pstate->nbatch = nbatch;
 	batches = dsa_get_address(hashtable->area, pstate->batches);
 
-	/* Use hash join memory context. */
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
+	/*
+	 * Use hash join spill memory context to allocate accessors and their
+	 * buffers.
+	 */
+	oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
 
 	/* Allocate this backend's accessor array. */
 	hashtable->nbatch = nbatch;
@@ -3196,8 +3213,8 @@ ExecParallelHashEnsureBatchAccessors(HashJoinTable hashtable)
 	 */
 	Assert(DsaPointerIsValid(pstate->batches));
 
-	/* Use hash join memory context. */
-	oldcxt = MemoryContextSwitchTo(hashtable->hashCxt);
+	/* Use hash join spill memory context to allocate accessors. */
+	oldcxt = MemoryContextSwitchTo(hashtable->spillCxt);
 
 	/* Allocate this backend's accessor array. */
 	hashtable->nbatch = pstate->nbatch;
diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c
index 78e202b4f9..1092a33525 100644
--- a/src/backend/executor/nodeHashjoin.c
+++ b/src/backend/executor/nodeHashjoin.c
@@ -489,7 +489,8 @@ ExecHashJoinImpl(PlanState *pstate, bool parallel)
 					Assert(parallel_state == NULL);
 					Assert(batchno > hashtable->curbatch);
 					ExecHashJoinSaveTuple(mintuple, hashvalue,
-										  &hashtable->outerBatchFile[batchno]);
+										  &hashtable->outerBatchFile[batchno],
+										  hashtable);
 
 					if (shouldFree)
 						heap_free_minimal_tuple(mintuple);
@@ -1310,22 +1311,38 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate)
  *
  * The data recorded in the file for each tuple is its hash value,
  * then the tuple in MinimalTuple format.
- *
- * Note: it is important always to call this in the regular executor
- * context, not in a shorter-lived context; else the temp file buffers
- * will get messed up.
  */
 void
 ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
-					  BufFile **fileptr)
+					  BufFile **fileptr, HashJoinTable hashtable)
 {
 	BufFile    *file = *fileptr;
 
 	if (file == NULL)
 	{
-		/* First write to this batch file, so open it. */
+		MemoryContext oldctx;
+
+		/*
+		 * The batch file is lazily created. If this is the first tuple
+		 * written to this batch, the batch file is created and its buffer is
+		 * allocated in the spillCxt context, NOT in the batchCxt.
+		 *
+		 * During the building phase, inner batch are created with their temp
+		 * file buffers. These buffers are released later, after the batch is
+		 * loaded back to memory during the outer side scan. That explains why
+		 * it is important to use a memory context which live longer than the
+		 * batch itself or some temp file buffers will get messed up.
+		 *
+		 * Also, we use spillCxt instead of hashCxt for a better accounting of
+		 * the spilling memory consumption.
+		 */
+
+		oldctx = MemoryContextSwitchTo(hashtable->spillCxt);
+
 		file = BufFileCreateTemp(false);
 		*fileptr = file;
+
+		MemoryContextSwitchTo(oldctx);
 	}
 
 	BufFileWrite(file, &hashvalue, sizeof(uint32));
diff --git a/src/backend/utils/sort/sharedtuplestore.c b/src/backend/utils/sort/sharedtuplestore.c
index 0831249159..236be65f22 100644
--- a/src/backend/utils/sort/sharedtuplestore.c
+++ b/src/backend/utils/sort/sharedtuplestore.c
@@ -308,11 +308,15 @@ sts_puttuple(SharedTuplestoreAccessor *accessor, void *meta_data,
 	{
 		SharedTuplestoreParticipant *participant;
 		char		name[MAXPGPATH];
+		MemoryContext oldcxt;
 
 		/* Create one.  Only this backend will write into it. */
 		sts_filename(name, accessor, accessor->participant);
+
+		oldcxt = MemoryContextSwitchTo(accessor->context);
 		accessor->write_file =
 			BufFileCreateFileSet(&accessor->fileset->fs, name);
+		MemoryContextSwitchTo(oldcxt);
 
 		/* Set up the shared state for this backend's file. */
 		participant = &accessor->sts->participants[accessor->participant];
@@ -527,11 +531,15 @@ sts_parallel_scan_next(SharedTuplestoreAccessor *accessor, void *meta_data)
 			if (accessor->read_file == NULL)
 			{
 				char		name[MAXPGPATH];
+				MemoryContext oldcxt;
 
 				sts_filename(name, accessor, accessor->read_participant);
+
+				oldcxt = MemoryContextSwitchTo(accessor->context);
 				accessor->read_file =
 					BufFileOpenFileSet(&accessor->fileset->fs, name, O_RDONLY,
 									   false);
+				MemoryContextSwitchTo(oldcxt);
 			}
 
 			/* Seek and load the chunk header. */
diff --git a/src/include/executor/hashjoin.h b/src/include/executor/hashjoin.h
index 8ee59d2c71..857ca58f6f 100644
--- a/src/include/executor/hashjoin.h
+++ b/src/include/executor/hashjoin.h
@@ -23,12 +23,12 @@
 /* ----------------------------------------------------------------
  *				hash-join hash table structures
  *
- * Each active hashjoin has a HashJoinTable control block, which is
- * palloc'd in the executor's per-query context.  All other storage needed
- * for the hashjoin is kept in private memory contexts, two for each hashjoin.
- * This makes it easy and fast to release the storage when we don't need it
- * anymore.  (Exception: data associated with the temp files lives in the
- * per-query context too, since we always call buffile.c in that context.)
+ * Each active hashjoin has a HashJoinTable structure, which is
+ * palloc'd in the executor's per-query context.  Other storage needed for
+ * each hashjoin is kept in child contexts, three for each hashjoin:
+ *   - HashTableContext (hashCxt): the parent hash table storage context
+ *   - HashSpillContext (spillCxt): storage for temp files buffers
+ *   - HashBatchContext (batchCxt): storage for a batch in serial hash join
  *
  * The hashtable contexts are made children of the per-query context, ensuring
  * that they will be discarded at end of statement even if the join is
@@ -36,9 +36,20 @@
  * be cleaned up by the virtual file manager in event of an error.)
  *
  * Storage that should live through the entire join is allocated from the
- * "hashCxt", while storage that is only wanted for the current batch is
- * allocated in the "batchCxt".  By resetting the batchCxt at the end of
- * each batch, we free all the per-batch storage reliably and without tedium.
+ * "hashCxt" (mainly the hashtable's metadata). Also, the "hashCxt" context is
+ * the parent of "spillCxt" and "batchCxt". It makes it easy and fast to
+ * release the storage when we don't need it anymore.
+ *
+ * Data associated with temp files is allocated in the "spillCxt" context
+ * which lives for the duration of the entire join as batch files'
+ * creation and usage may span batch execution. These files are
+ * explicitly destroyed by calling BufFileClose() when the code is done
+ * with them. The aim of this context is to help accounting for the
+ * memory allocated for temp files and their buffers.
+ *
+ * Finally, data used only during a single batch's execution is allocated
+ * in the "batchCxt". By resetting the batchCxt at the end of each batch,
+ * we free all the per-batch storage reliably and without tedium.
  *
  * During first scan of inner relation, we get its tuples from executor.
  * If nbatch > 1 then tuples that don't belong in first batch get saved
@@ -350,6 +361,7 @@ typedef struct HashJoinTableData
 
 	MemoryContext hashCxt;		/* context for whole-hash-join storage */
 	MemoryContext batchCxt;		/* context for this-batch-only storage */
+	MemoryContext spillCxt;		/* context for spilling to temp files */
 
 	/* used for dense allocation of tuples (into linked chunks) */
 	HashMemoryChunk chunks;		/* one list for the whole batch */
diff --git a/src/include/executor/nodeHashjoin.h b/src/include/executor/nodeHashjoin.h
index d367070883..ccb704ede1 100644
--- a/src/include/executor/nodeHashjoin.h
+++ b/src/include/executor/nodeHashjoin.h
@@ -29,6 +29,6 @@ extern void ExecHashJoinInitializeWorker(HashJoinState *state,
 										 ParallelWorkerContext *pwcxt);
 
 extern void ExecHashJoinSaveTuple(MinimalTuple tuple, uint32 hashvalue,
-								  BufFile **fileptr);
+								  BufFile **fileptr, HashJoinTable hashtable);
 
 #endif							/* NODEHASHJOIN_H */
-- 
2.40.1


--MP_/CAw=Cm.TBs/NMHAJ6DWYJQ5
Content-Type: text/x-patch
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
 filename=v9-0003-Run-pgindent-on-nodeHash.c-and-nodeHashjoin.c.patch



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

* [PATCH v19 5/8] Row pattern recognition patch (executor).
@ 2024-05-14 23:26 Tatsuo Ishii <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

From: Tatsuo Ishii @ 2024-05-14 23:26 UTC (permalink / raw)

---
 src/backend/executor/nodeWindowAgg.c | 1610 +++++++++++++++++++++++++-
 src/backend/utils/adt/windowfuncs.c  |   37 +-
 src/include/catalog/pg_proc.dat      |    6 +
 src/include/nodes/execnodes.h        |   30 +
 4 files changed, 1671 insertions(+), 12 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 3221fa1522..140bb3941e 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -36,6 +36,7 @@
 #include "access/htup_details.h"
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/pg_proc.h"
 #include "executor/executor.h"
 #include "executor/nodeWindowAgg.h"
@@ -48,6 +49,7 @@
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/datum.h"
+#include "utils/fmgroids.h"
 #include "utils/expandeddatum.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -159,6 +161,43 @@ typedef struct WindowStatePerAggData
 	bool		restart;		/* need to restart this agg in this cycle? */
 } WindowStatePerAggData;
 
+/*
+ * Set of StringInfo. Used in RPR.
+ */
+typedef struct StringSet
+{
+	StringInfo *str_set;
+	Size		set_size;		/* current array allocation size in number of
+								 * items */
+	int			set_index;		/* current used size */
+}			StringSet;
+
+/*
+ * Allowed subsequent PATTERN variables positions.
+ * Used in RPR.
+ *
+ * pos represents the pattern variable defined order in DEFINE caluase.  For
+ * example. "DEFINE START..., UP..., DOWN ..." and "PATTERN START UP DOWN UP"
+ * will create:
+ * VariablePos[0].pos[0] = 0;		START
+ * VariablePos[1].pos[0] = 1;		UP
+ * VariablePos[1].pos[1] = 3;		UP
+ * VariablePos[2].pos[0] = 2;		DOWN
+ *
+ * Note that UP has two pos because UP appears in PATTERN twice.
+ *
+ * By using this strucrture, we can know which pattern variable can be followed
+ * by which pattern variable(s). For example, START can be followed by UP and
+ * DOWN since START's pos is 0, and UP's pos is 1 or 3, DOWN's pos is 2.
+ * DOWN can be followed by UP since UP's pos is either 1 or 3.
+ *
+ */
+#define NUM_ALPHABETS	26		/* we allow [a-z] variable initials */
+typedef struct VariablePos
+{
+	int			pos[NUM_ALPHABETS]; /* postion(s) in PATTERN */
+}			VariablePos;
+
 static void initialize_windowaggregate(WindowAggState *winstate,
 									   WindowStatePerFunc perfuncstate,
 									   WindowStatePerAgg peraggstate);
@@ -184,6 +223,7 @@ static void release_partition(WindowAggState *winstate);
 
 static int	row_is_in_frame(WindowAggState *winstate, int64 pos,
 							TupleTableSlot *slot);
+
 static void update_frameheadpos(WindowAggState *winstate);
 static void update_frametailpos(WindowAggState *winstate);
 static void update_grouptailpos(WindowAggState *winstate);
@@ -195,9 +235,48 @@ static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
 
 static bool are_peers(WindowAggState *winstate, TupleTableSlot *slot1,
 					  TupleTableSlot *slot2);
+
+static int	WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+							  int relpos, int seektype, bool set_mark,
+							  bool *isnull, bool *isout);
 static bool window_gettupleslot(WindowObject winobj, int64 pos,
 								TupleTableSlot *slot);
 
+static void attno_map(Node *node);
+static bool attno_map_walker(Node *node, void *context);
+static int	row_is_in_reduced_frame(WindowObject winobj, int64 pos);
+static bool rpr_is_defined(WindowAggState *winstate);
+
+static void create_reduced_frame_map(WindowAggState *winstate);
+static int	get_reduced_frame_map(WindowAggState *winstate, int64 pos);
+static void register_reduced_frame_map(WindowAggState *winstate, int64 pos,
+									   int val);
+static void clear_reduced_frame_map(WindowAggState *winstate);
+static void update_reduced_frame(WindowObject winobj, int64 pos);
+
+static int64 evaluate_pattern(WindowObject winobj, int64 current_pos,
+							  char *vname, StringInfo encoded_str, bool *result);
+
+static bool get_slots(WindowObject winobj, int64 current_pos);
+
+static int	search_str_set(char *pattern, StringSet * str_set,
+						   VariablePos * variable_pos);
+static char pattern_initial(WindowAggState *winstate, char *vname);
+static int	do_pattern_match(char *pattern, char *encoded_str);
+
+static StringSet * string_set_init(void);
+static void string_set_add(StringSet * string_set, StringInfo str);
+static StringInfo string_set_get(StringSet * string_set, int index);
+static int	string_set_get_size(StringSet * string_set);
+static void string_set_discard(StringSet * string_set);
+static VariablePos * variable_pos_init(void);
+static void variable_pos_register(VariablePos * variable_pos, char initial,
+								  int pos);
+static bool variable_pos_compare(VariablePos * variable_pos,
+								 char initial1, char initial2);
+static int	variable_pos_fetch(VariablePos * variable_pos, char initial,
+							   int index);
+static void variable_pos_discard(VariablePos * variable_pos);
 
 /*
  * initialize_windowaggregate
@@ -774,10 +853,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	 *	   transition function, or
 	 *	 - we have an EXCLUSION clause, or
 	 *	 - if the new frame doesn't overlap the old one
+	 *   - if RPR is enabled
 	 *
 	 * Note that we don't strictly need to restart in the last case, but if
 	 * we're going to remove all rows from the aggregation anyway, a restart
 	 * surely is faster.
+	 *     we restart aggregation too.
 	 *----------
 	 */
 	numaggs_restart = 0;
@@ -788,7 +869,8 @@ eval_windowaggregates(WindowAggState *winstate)
 			(winstate->aggregatedbase != winstate->frameheadpos &&
 			 !OidIsValid(peraggstate->invtransfn_oid)) ||
 			(winstate->frameOptions & FRAMEOPTION_EXCLUSION) ||
-			winstate->aggregatedupto <= winstate->frameheadpos)
+			winstate->aggregatedupto <= winstate->frameheadpos ||
+			rpr_is_defined(winstate))
 		{
 			peraggstate->restart = true;
 			numaggs_restart++;
@@ -862,7 +944,22 @@ eval_windowaggregates(WindowAggState *winstate)
 	 * head, so that tuplestore can discard unnecessary rows.
 	 */
 	if (agg_winobj->markptr >= 0)
-		WinSetMarkPosition(agg_winobj, winstate->frameheadpos);
+	{
+		int64		markpos = winstate->frameheadpos;
+
+		if (rpr_is_defined(winstate))
+		{
+			/*
+			 * If RPR is used, it is possible PREV wants to look at the
+			 * previous row.  So the mark pos should be frameheadpos - 1
+			 * unless it is below 0.
+			 */
+			markpos -= 1;
+			if (markpos < 0)
+				markpos = 0;
+		}
+		WinSetMarkPosition(agg_winobj, markpos);
+	}
 
 	/*
 	 * Now restart the aggregates that require it.
@@ -917,6 +1014,14 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		winstate->aggregatedupto = winstate->frameheadpos;
 		ExecClearTuple(agg_row_slot);
+
+		/*
+		 * If RPR is defined, we do not use aggregatedupto_nonrestarted.  To
+		 * avoid assertion failure below, we reset aggregatedupto_nonrestarted
+		 * to frameheadpos.
+		 */
+		if (rpr_is_defined(winstate))
+			aggregatedupto_nonrestarted = winstate->frameheadpos;
 	}
 
 	/*
@@ -930,6 +1035,12 @@ eval_windowaggregates(WindowAggState *winstate)
 	{
 		int			ret;
 
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "===== loop in frame starts: aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+			 winstate->aggregatedupto,
+			 winstate->aggregatedbase);
+#endif
+
 		/* Fetch next row if we didn't already */
 		if (TupIsNull(agg_row_slot))
 		{
@@ -945,9 +1056,52 @@ eval_windowaggregates(WindowAggState *winstate)
 		ret = row_is_in_frame(winstate, winstate->aggregatedupto, agg_row_slot);
 		if (ret < 0)
 			break;
+
 		if (ret == 0)
 			goto next_tuple;
 
+		if (rpr_is_defined(winstate))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "reduced_frame_map: %d aggregatedupto: " INT64_FORMAT " aggregatedbase: " INT64_FORMAT,
+				 get_reduced_frame_map(winstate,
+									   winstate->aggregatedupto),
+				 winstate->aggregatedupto,
+				 winstate->aggregatedbase);
+#endif
+			/*
+			 * If the row status at currentpos is already decided and current
+			 * row status is not decided yet, it means we passed the last
+			 * reduced frame. Time to break the loop.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->currentpos) != RF_NOT_DETERMINED &&
+				get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_NOT_DETERMINED)
+				break;
+
+			/*
+			 * Otherwise we need to calculate the reduced frame.
+			 */
+			ret = row_is_in_reduced_frame(winstate->agg_winobj,
+										  winstate->aggregatedupto);
+			if (ret == -1)		/* unmatched row */
+				break;
+
+			/*
+			 * Check if current row needs to be skipped due to no match.
+			 */
+			if (get_reduced_frame_map(winstate,
+									  winstate->aggregatedupto) == RF_SKIPPED &&
+				winstate->aggregatedupto == winstate->aggregatedbase)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "skip current row for aggregation");
+#endif
+				break;
+			}
+		}
+
 		/* Set tuple context for evaluation of aggregate arguments */
 		winstate->tmpcontext->ecxt_outertuple = agg_row_slot;
 
@@ -976,6 +1130,7 @@ next_tuple:
 		ExecClearTuple(agg_row_slot);
 	}
 
+
 	/* The frame's end is not supposed to move backwards, ever */
 	Assert(aggregatedupto_nonrestarted <= winstate->aggregatedupto);
 
@@ -995,7 +1150,6 @@ next_tuple:
 								 &winstate->perfunc[wfuncno],
 								 peraggstate,
 								 result, isnull);
-
 		/*
 		 * save the result in case next row shares the same frame.
 		 *
@@ -1090,6 +1244,7 @@ begin_partition(WindowAggState *winstate)
 	winstate->framehead_valid = false;
 	winstate->frametail_valid = false;
 	winstate->grouptail_valid = false;
+	create_reduced_frame_map(winstate);
 	winstate->spooled_rows = 0;
 	winstate->currentpos = 0;
 	winstate->frameheadpos = 0;
@@ -2053,6 +2208,11 @@ ExecWindowAgg(PlanState *pstate)
 
 	CHECK_FOR_INTERRUPTS();
 
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "ExecWindowAgg called. pos: " INT64_FORMAT,
+		 winstate->currentpos);
+#endif
+
 	if (winstate->status == WINDOWAGG_DONE)
 		return NULL;
 
@@ -2221,6 +2381,17 @@ ExecWindowAgg(PlanState *pstate)
 		/* don't evaluate the window functions when we're in pass-through mode */
 		if (winstate->status == WINDOWAGG_RUN)
 		{
+			/*
+			 * If RPR is defined and skip mode is next row, we need to clear
+			 * existing reduced frame info so that we newly calculate the info
+			 * starting from current row.
+			 */
+			if (rpr_is_defined(winstate))
+			{
+				if (winstate->rpSkipTo == ST_NEXT_ROW)
+					clear_reduced_frame_map(winstate);
+			}
+
 			/*
 			 * Evaluate true window functions
 			 */
@@ -2388,6 +2559,9 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	TupleDesc	scanDesc;
 	ListCell   *l;
 
+	TargetEntry *te;
+	Expr	   *expr;
+
 	/* check for unsupported flags */
 	Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
 
@@ -2486,6 +2660,16 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->temp_slot_2 = ExecInitExtraTupleSlot(estate, scanDesc,
 												   &TTSOpsMinimalTuple);
 
+	winstate->prev_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->next_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+
+	winstate->null_slot = ExecInitExtraTupleSlot(estate, scanDesc,
+												 &TTSOpsMinimalTuple);
+	winstate->null_slot = ExecStoreAllNullTuple(winstate->null_slot);
+
 	/*
 	 * create frame head and tail slots only if needed (must create slots in
 	 * exactly the same cases that update_frameheadpos and update_frametailpos
@@ -2667,6 +2851,43 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	winstate->inRangeAsc = node->inRangeAsc;
 	winstate->inRangeNullsFirst = node->inRangeNullsFirst;
 
+	/* Set up SKIP TO type */
+	winstate->rpSkipTo = node->rpSkipTo;
+	/* Set up row pattern recognition PATTERN clause */
+	winstate->patternVariableList = node->patternVariable;
+	winstate->patternRegexpList = node->patternRegexp;
+
+	/* Set up row pattern recognition DEFINE clause */
+	winstate->defineInitial = node->defineInitial;
+	winstate->defineVariableList = NIL;
+	winstate->defineClauseList = NIL;
+	if (node->defineClause != NIL)
+	{
+		/*
+		 * Tweak arg var of PREV/NEXT so that it refers to scan/inner slot.
+		 */
+		foreach(l, node->defineClause)
+		{
+			char	   *name;
+			ExprState  *exps;
+
+			te = lfirst(l);
+			name = te->resname;
+			expr = te->expr;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "defineVariable name: %s", name);
+#endif
+			winstate->defineVariableList =
+				lappend(winstate->defineVariableList,
+						makeString(pstrdup(name)));
+			attno_map((Node *) expr);
+			exps = ExecInitExpr(expr, (PlanState *) winstate);
+			winstate->defineClauseList =
+				lappend(winstate->defineClauseList, exps);
+		}
+	}
+
 	winstate->all_first = true;
 	winstate->partition_spooled = false;
 	winstate->more_partitions = false;
@@ -2674,6 +2895,64 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	return winstate;
 }
 
+/*
+ * Rewrite varno of Var node that is the argument of PREV/NET so that it sees
+ * scan tuple (PREV) or inner tuple (NEXT).
+ */
+static void
+attno_map(Node *node)
+{
+	(void) expression_tree_walker(node, attno_map_walker, NULL);
+}
+
+static bool
+attno_map_walker(Node *node, void *context)
+{
+	FuncExpr   *func;
+	int			nargs;
+	Expr	   *expr;
+	Var		   *var;
+
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, FuncExpr))
+	{
+		func = (FuncExpr *) node;
+
+		if (func->funcid == F_PREV || func->funcid == F_NEXT)
+		{
+			/* sanity check */
+			nargs = list_length(func->args);
+			if (list_length(func->args) != 1)
+				elog(ERROR, "PREV/NEXT must have 1 argument but function %d has %d args",
+					 func->funcid, nargs);
+
+			expr = (Expr *) lfirst(list_head(func->args));
+			if (!IsA(expr, Var))
+				elog(ERROR, "PREV/NEXT's arg is not Var");	/* XXX: is it possible
+															 * that arg type is
+															 * Const? */
+			var = (Var *) expr;
+
+			if (func->funcid == F_PREV)
+
+				/*
+				 * Rewrite varno from OUTER_VAR to regular var no so that the
+				 * var references scan tuple.
+				 */
+				var->varno = var->varnosyn;
+			else
+				var->varno = INNER_VAR;
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "PREV/NEXT's varno is rewritten to: %d", var->varno);
+#endif
+		}
+	}
+	return expression_tree_walker(node, attno_map_walker, NULL);
+}
+
 /* -----------------
  * ExecEndWindowAgg
  * -----------------
@@ -2723,6 +3002,8 @@ ExecReScanWindowAgg(WindowAggState *node)
 	ExecClearTuple(node->agg_row_slot);
 	ExecClearTuple(node->temp_slot_1);
 	ExecClearTuple(node->temp_slot_2);
+	ExecClearTuple(node->prev_slot);
+	ExecClearTuple(node->next_slot);
 	if (node->framehead_slot)
 		ExecClearTuple(node->framehead_slot);
 	if (node->frametail_slot)
@@ -3083,7 +3364,8 @@ window_gettupleslot(WindowObject winobj, int64 pos, TupleTableSlot *slot)
 		return false;
 
 	if (pos < winobj->markpos)
-		elog(ERROR, "cannot fetch row before WindowObject's mark position");
+		elog(ERROR, "cannot fetch row: " INT64_FORMAT " before WindowObject's mark position: " INT64_FORMAT,
+			 pos, winobj->markpos);
 
 	oldcontext = MemoryContextSwitchTo(winstate->ss.ps.ps_ExprContext->ecxt_per_query_memory);
 
@@ -3403,14 +3685,54 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 	WindowAggState *winstate;
 	ExprContext *econtext;
 	TupleTableSlot *slot;
-	int64		abs_pos;
-	int64		mark_pos;
 
 	Assert(WindowObjectIsValid(winobj));
 	winstate = winobj->winstate;
 	econtext = winstate->ss.ps.ps_ExprContext;
 	slot = winstate->temp_slot_1;
 
+	if (WinGetSlotInFrame(winobj, slot,
+						  relpos, seektype, set_mark,
+						  isnull, isout) == 0)
+	{
+		econtext->ecxt_outertuple = slot;
+		return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
+							econtext, isnull);
+	}
+
+	if (isout)
+		*isout = true;
+	*isnull = true;
+	return (Datum) 0;
+}
+
+/*
+ * WinGetSlotInFrame
+ * slot: TupleTableSlot to store the result
+ * relpos: signed rowcount offset from the seek position
+ * seektype: WINDOW_SEEK_HEAD or WINDOW_SEEK_TAIL
+ * set_mark: If the row is found/in frame and set_mark is true, the mark is
+ *		moved to the row as a side-effect.
+ * isnull: output argument, receives isnull status of result
+ * isout: output argument, set to indicate whether target row position
+ *		is out of frame (can pass NULL if caller doesn't care about this)
+ *
+ * Returns 0 if we successfullt got the slot. false if out of frame.
+ * (also isout is set)
+ */
+static int
+WinGetSlotInFrame(WindowObject winobj, TupleTableSlot *slot,
+				  int relpos, int seektype, bool set_mark,
+				  bool *isnull, bool *isout)
+{
+	WindowAggState *winstate;
+	int64		abs_pos;
+	int64		mark_pos;
+	int			num_reduced_frame;
+
+	Assert(WindowObjectIsValid(winobj));
+	winstate = winobj->winstate;
+
 	switch (seektype)
 	{
 		case WINDOW_SEEK_CURRENT:
@@ -3477,11 +3799,25 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 						 winstate->frameOptions);
 					break;
 			}
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				if (relpos >= num_reduced_frame)
+					goto out_of_frame;
 			break;
 		case WINDOW_SEEK_TAIL:
 			/* rejecting relpos > 0 is easy and simplifies code below */
 			if (relpos > 0)
 				goto out_of_frame;
+
+			/*
+			 * RPR cares about frame head pos. Need to call
+			 * update_frameheadpos
+			 */
+			update_frameheadpos(winstate);
+
 			update_frametailpos(winstate);
 			abs_pos = winstate->frametailpos - 1 + relpos;
 
@@ -3548,6 +3884,14 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 					mark_pos = 0;	/* keep compiler quiet */
 					break;
 			}
+
+			num_reduced_frame = row_is_in_reduced_frame(winobj,
+														winstate->frameheadpos + relpos);
+			if (num_reduced_frame < 0)
+				goto out_of_frame;
+			else if (num_reduced_frame > 0)
+				abs_pos = winstate->frameheadpos + relpos +
+					num_reduced_frame - 1;
 			break;
 		default:
 			elog(ERROR, "unrecognized window seek type: %d", seektype);
@@ -3566,15 +3910,13 @@ WinGetFuncArgInFrame(WindowObject winobj, int argno,
 		*isout = false;
 	if (set_mark)
 		WinSetMarkPosition(winobj, mark_pos);
-	econtext->ecxt_outertuple = slot;
-	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
-						econtext, isnull);
+	return 0;
 
 out_of_frame:
 	if (isout)
 		*isout = true;
 	*isnull = true;
-	return (Datum) 0;
+	return -1;
 }
 
 /*
@@ -3605,3 +3947,1251 @@ WinGetFuncArgCurrent(WindowObject winobj, int argno, bool *isnull)
 	return ExecEvalExpr((ExprState *) list_nth(winobj->argstates, argno),
 						econtext, isnull);
 }
+
+/*
+ * rpr_is_defined
+ * return true if Row pattern recognition is defined.
+ */
+static
+bool
+rpr_is_defined(WindowAggState *winstate)
+{
+	return winstate->patternVariableList != NIL;
+}
+
+/*
+ * -----------------
+ * row_is_in_reduced_frame
+ * Determine whether a row is in the current row's reduced window frame
+ * according to row pattern matching
+ *
+ * The row must has been already determined that it is in a full window frame
+ * and fetched it into slot.
+ *
+ * Returns:
+ * = 0, RPR is not defined.
+ * >0, if the row is the first in the reduced frame. Return the number of rows
+ * in the reduced frame.
+ * -1, if the row is unmatched row
+ * -2, if the row is in the reduced frame but needed to be skipped because of
+ * AFTER MATCH SKIP PAST LAST ROW
+ * -----------------
+ */
+static
+int
+row_is_in_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	int			state;
+	int			rtn;
+
+	if (!rpr_is_defined(winstate))
+	{
+		/*
+		 * RPR is not defined. Assume that we are always in the the reduced
+		 * window frame.
+		 */
+		rtn = 0;
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+			 rtn, pos);
+#endif
+		return rtn;
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	if (state == RF_NOT_DETERMINED)
+	{
+		update_frameheadpos(winstate);
+		update_reduced_frame(winobj, pos);
+	}
+
+	state = get_reduced_frame_map(winstate, pos);
+
+	switch (state)
+	{
+			int64		i;
+			int			num_reduced_rows;
+
+		case RF_FRAME_HEAD:
+			num_reduced_rows = 1;
+			for (i = pos + 1;
+				 get_reduced_frame_map(winstate, i) == RF_SKIPPED; i++)
+				num_reduced_rows++;
+			rtn = num_reduced_rows;
+			break;
+
+		case RF_SKIPPED:
+			rtn = -2;
+			break;
+
+		case RF_UNMATCHED:
+			rtn = -1;
+			break;
+
+		default:
+			elog(ERROR, "Unrecognized state: %d at: " INT64_FORMAT,
+				 state, pos);
+			break;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "row_is_in_reduced_frame returns %d: pos: " INT64_FORMAT,
+		 rtn, pos);
+#endif
+	return rtn;
+}
+
+#define REDUCED_FRAME_MAP_INIT_SIZE	1024L
+
+/*
+ * create_reduced_frame_map
+ * Create reduced frame map
+ */
+static
+void
+create_reduced_frame_map(WindowAggState *winstate)
+{
+	winstate->reduced_frame_map =
+		MemoryContextAlloc(winstate->partcontext,
+						   REDUCED_FRAME_MAP_INIT_SIZE);
+	winstate->alloc_sz = REDUCED_FRAME_MAP_INIT_SIZE;
+	clear_reduced_frame_map(winstate);
+}
+
+/*
+ * clear_reduced_frame_map
+ * Clear reduced frame map
+ */
+static
+void
+clear_reduced_frame_map(WindowAggState *winstate)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+	MemSet(winstate->reduced_frame_map, RF_NOT_DETERMINED,
+		   winstate->alloc_sz);
+}
+
+/*
+ * get_reduced_frame_map
+ * Get reduced frame map specified by pos
+ */
+static
+int
+get_reduced_frame_map(WindowAggState *winstate, int64 pos)
+{
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0 || pos >= winstate->alloc_sz)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	return winstate->reduced_frame_map[pos];
+}
+
+/*
+ * register_reduced_frame_map
+ * Add/replace reduced frame map member at pos.
+ * If there's no enough space, expand the map.
+ */
+static
+void
+register_reduced_frame_map(WindowAggState *winstate, int64 pos, int val)
+{
+	int64		realloc_sz;
+
+	Assert(winstate->reduced_frame_map != NULL);
+
+	if (pos < 0)
+		elog(ERROR, "wrong pos: " INT64_FORMAT, pos);
+
+	if (pos > winstate->alloc_sz - 1)
+	{
+		realloc_sz = winstate->alloc_sz * 2;
+
+		winstate->reduced_frame_map =
+			repalloc(winstate->reduced_frame_map, realloc_sz);
+
+		MemSet(winstate->reduced_frame_map + winstate->alloc_sz,
+			   RF_NOT_DETERMINED, realloc_sz - winstate->alloc_sz);
+
+		winstate->alloc_sz = realloc_sz;
+	}
+
+	winstate->reduced_frame_map[pos] = val;
+}
+
+/*
+ * update_reduced_frame
+ *		Update reduced frame info.
+ */
+static
+void
+update_reduced_frame(WindowObject winobj, int64 pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ListCell   *lc1,
+			   *lc2;
+	bool		expression_result;
+	int			num_matched_rows;
+	int64		original_pos;
+	bool		anymatch;
+	StringInfo	encoded_str;
+	StringInfo	pattern_str = makeStringInfo();
+	StringSet  *str_set;
+	int			initial_index;
+	VariablePos *variable_pos;
+	bool		greedy = false;
+	int64		result_pos,
+				i;
+
+	/*
+	 * Set of pattern variables evaluated to true. Each character corresponds
+	 * to pattern variable. Example: str_set[0] = "AB"; str_set[1] = "AC"; In
+	 * this case at row 0 A and B are true, and A and C are true in row 1.
+	 */
+
+	/* initialize pattern variables set */
+	str_set = string_set_init();
+
+	/* save original pos */
+	original_pos = pos;
+
+	/*
+	 * Check if the pattern does not include any greedy quantifier. If it does
+	 * not, we can just apply the pattern to each row. If it succeeds, we are
+	 * done.
+	 */
+	foreach(lc1, winstate->patternRegexpList)
+	{
+		char	   *quantifier = strVal(lfirst(lc1));
+
+		if (*quantifier == '+' || *quantifier == '*')
+		{
+			greedy = true;
+			break;
+		}
+	}
+
+	/*
+	 * Non greedy case
+	 */
+	if (!greedy)
+	{
+		num_matched_rows = 0;
+
+		foreach(lc1, winstate->patternVariableList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+
+			encoded_str = makeStringInfo();
+
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s",
+				 pos, vname);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (!expression_result || result_pos < 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is false or out of frame");
+#endif
+				register_reduced_frame_map(winstate, original_pos,
+										   RF_UNMATCHED);
+				return;
+			}
+			/* move to next row */
+			pos++;
+			num_matched_rows++;
+		}
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pattern matched");
+#endif
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+		return;
+	}
+
+	/*
+	 * Greedy quantifiers included. Loop over until none of pattern matches or
+	 * encounters end of frame.
+	 */
+	for (;;)
+	{
+		result_pos = -1;
+
+		/*
+		 * Loop over each PATTERN variable.
+		 */
+		anymatch = false;
+		encoded_str = makeStringInfo();
+
+		forboth(lc1, winstate->patternVariableList, lc2,
+				winstate->patternRegexpList)
+		{
+			char	   *vname = strVal(lfirst(lc1));
+#ifdef RPR_DEBUG
+			char	   *quantifier = strVal(lfirst(lc2));
+
+			elog(DEBUG1, "pos: " INT64_FORMAT " pattern vname: %s quantifier: %s",
+				 pos, vname, quantifier);
+#endif
+			expression_result = false;
+
+			/* evaluate row pattern against current row */
+			result_pos = evaluate_pattern(winobj, pos, vname,
+										  encoded_str, &expression_result);
+			if (expression_result)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression result is true");
+#endif
+				anymatch = true;
+			}
+
+			/*
+			 * If out of frame, we are done.
+			 */
+			if (result_pos < 0)
+				break;
+		}
+
+		if (!anymatch)
+		{
+			/* none of patterns matched. */
+			break;
+		}
+
+		string_set_add(str_set, encoded_str);
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "pos: " INT64_FORMAT " encoded_str: %s",
+			 encoded_str->data);
+#endif
+
+		/* move to next row */
+		pos++;
+
+		if (result_pos < 0)
+		{
+			/* out of frame */
+			break;
+		}
+	}
+
+	if (string_set_get_size(str_set) == 0)
+	{
+		/* no match found in the first row */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+		return;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " encoded_str: %s",
+		 pos, encoded_str->data);
+#endif
+
+	/* build regular expression */
+	pattern_str = makeStringInfo();
+	appendStringInfoChar(pattern_str, '^');
+	initial_index = 0;
+
+	variable_pos = variable_pos_init();
+
+	forboth(lc1, winstate->patternVariableList,
+			lc2, winstate->patternRegexpList)
+	{
+		char	   *vname = strVal(lfirst(lc1));
+		char	   *quantifier = strVal(lfirst(lc2));
+		char		initial;
+
+		initial = pattern_initial(winstate, vname);
+		Assert(initial != 0);
+		appendStringInfoChar(pattern_str, initial);
+		if (quantifier[0])
+			appendStringInfoChar(pattern_str, quantifier[0]);
+
+		/*
+		 * Register the initial at initial_index. If the initial appears more
+		 * than once, all of it's initial_index will be recorded. This could
+		 * happen if a pattern variable appears in the PATTERN clause more
+		 * than once like "UP DOWN UP" "UP UP UP".
+		 */
+		variable_pos_register(variable_pos, initial, initial_index);
+
+		initial_index++;
+	}
+
+#ifdef RPR_DEBUG
+	elog(DEBUG2, "pos: " INT64_FORMAT " pattern: %s",
+		 pos, pattern_str->data);
+#endif
+
+	/* look for matching pattern variable sequence */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set started");
+#endif
+	num_matched_rows = search_str_set(pattern_str->data,
+									  str_set, variable_pos);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "search_str_set returns: %d", num_matched_rows);
+#endif
+	variable_pos_discard(variable_pos);
+	string_set_discard(str_set);
+
+	/*
+	 * We are at the first row in the reduced frame.  Save the number of
+	 * matched rows as the number of rows in the reduced frame.
+	 */
+	if (num_matched_rows <= 0)
+	{
+		/* no match */
+		register_reduced_frame_map(winstate, original_pos, RF_UNMATCHED);
+	}
+	else
+	{
+		register_reduced_frame_map(winstate, original_pos, RF_FRAME_HEAD);
+
+		for (i = original_pos + 1; i < original_pos + num_matched_rows; i++)
+		{
+			register_reduced_frame_map(winstate, i, RF_SKIPPED);
+		}
+	}
+
+	return;
+}
+
+/*
+ * search_str_set
+ * Perform pattern matching using "pattern" against str_set. pattern is a
+ * regular expression derived from PATTERN clause. Note that the regular
+ * expression string is prefixed by '^' and followed by initials represented
+ * in a same way as str_set. str_set is a set of StringInfo. Each StringInfo
+ * has a string comprising initials of pattern variable strings being true in
+ * a row. The initials are one of [a-y], parallel to the order of variable
+ * names in DEFINE clause. Suppose DEFINE has variables START, UP and DOWN. If
+ * PATTERN has START, UP+ and DOWN, then the initials in PATTERN will be 'a',
+ * 'b' and 'c'. The "pattern" will be "^ab+c".
+ *
+ * variable_pos is an array representing the order of pattern variable string
+ * initials in PATTERN clause.  For example initial 'a' potion is in
+ * variable_pos[0].pos[0] = 0. Note that if the pattern is "START UP DOWN UP"
+ * (UP appears twice), then "UP" (initial is 'b') has two position 1 and
+ * 3. Thus variable_pos for b is variable_pos[1].pos[0] = 1 and
+ * variable_pos[1].pos[1] = 3.
+ *
+ * Returns the longest number of the matching rows (greedy matching) if
+ * quatifier '+' or '*' is included in "pattern".
+ */
+static
+int
+search_str_set(char *pattern, StringSet * str_set, VariablePos * variable_pos)
+{
+#define	MAX_CANDIDATE_NUM	10000	/* max pattern match candidate size */
+#define	FREEZED_CHAR	'Z'		/* a pattern is freezed if it ends with the
+								 * char */
+#define	DISCARD_CHAR	'z'		/* a pattern is not need to keep */
+
+	int			set_size;		/* number of rows in the set */
+	int			resultlen;
+	int			index;
+	StringSet  *old_str_set,
+			   *new_str_set;
+	int			new_str_size;
+	int			len;
+
+	set_size = string_set_get_size(str_set);
+	new_str_set = string_set_init();
+	len = 0;
+	resultlen = 0;
+
+	/*
+	 * Generate all possible pattern variable name initials as a set of
+	 * StringInfo named "new_str_set".  For example, if we have two rows
+	 * having "ab" (row 0) and "ac" (row 1) in the input str_set, new_str_set
+	 * will have set of StringInfo "aa", "ac", "ba" and "bc" in the end.
+	 */
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "pattern: %s set_size: %d", pattern, set_size);
+#endif
+	for (index = 0; index < set_size; index++)
+	{
+		StringInfo	str;		/* search target row */
+		char	   *p;
+		int			old_set_size;
+		int			i;
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "index: %d", index);
+#endif
+		if (index == 0)
+		{
+			/* copy variables in row 0 */
+			str = string_set_get(str_set, index);
+			p = str->data;
+
+			/*
+			 * Loop over each new pattern variable char.
+			 */
+			while (*p)
+			{
+				StringInfo	new = makeStringInfo();
+
+				/* add pattern variable char */
+				appendStringInfoChar(new, *p);
+				/* add new one to string set */
+				string_set_add(new_str_set, new);
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "old_str: NULL new_str: %s", new->data);
+#endif
+				p++;			/* next pattern variable */
+			}
+		}
+		else					/* index != 0 */
+		{
+			old_str_set = new_str_set;
+			new_str_set = string_set_init();
+			str = string_set_get(str_set, index);
+			old_set_size = string_set_get_size(old_str_set);
+
+			/*
+			 * Loop over each rows in the previous result set.
+			 */
+			for (i = 0; i < old_set_size; i++)
+			{
+				StringInfo	new;
+				char		last_old_char;
+				int			old_str_len;
+				StringInfo	old = string_set_get(old_str_set, i);
+
+				p = old->data;
+				old_str_len = strlen(p);
+				if (old_str_len > 0)
+					last_old_char = p[old_str_len - 1];
+				else
+					last_old_char = '\0';
+
+				/* Is this old set freezed? */
+				if (last_old_char == FREEZED_CHAR)
+				{
+					/* if shorter match. we can discard it */
+					if ((old_str_len - 1) < resultlen)
+					{
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "discard this old set because shorter match: %s",
+							 old->data);
+#endif
+						continue;
+					}
+
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "keep this old set: %s", old->data);
+#endif
+
+					/* move the old set to new_str_set */
+					string_set_add(new_str_set, old);
+					old_str_set->str_set[i] = NULL;
+					continue;
+				}
+				/* Can this old set be discarded? */
+				else if (last_old_char == DISCARD_CHAR)
+				{
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "discard this old set: %s", old->data);
+#endif
+					continue;
+				}
+
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "str->data: %s", str->data);
+#endif
+
+				/*
+				 * loop over each pattern variable initial char in the input
+				 * set.
+				 */
+				for (p = str->data; *p; p++)
+				{
+					/*
+					 * Optimization.  Check if the row's pattern variable
+					 * initial character position is greater than or equal to
+					 * the old set's last pattern variable initial character
+					 * position. For example, if the old set's last pattern
+					 * variable initials are "ab", then the new pattern
+					 * variable initial can be "b" or "c" but can not be "a",
+					 * if the initials in PATTERN is something like "a b c" or
+					 * "a b+ c+" etc.  This optimization is possible when we
+					 * only allow "+" quantifier.
+					 */
+					if (variable_pos_compare(variable_pos, last_old_char, *p))
+					{
+						/* copy source string */
+						new = makeStringInfo();
+						enlargeStringInfo(new, old->len + 1);
+						appendStringInfoString(new, old->data);
+						/* add pattern variable char */
+						appendStringInfoChar(new, *p);
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "old_str: %s new_str: %s",
+							 old->data, new->data);
+#endif
+
+						/*
+						 * Adhoc optimization. If the first letter in the
+						 * input string is the first and second position one
+						 * and there's no associated quatifier '+', then we
+						 * can dicard the input because there's no chace to
+						 * expand the string further.
+						 *
+						 * For example, pattern "abc" cannot match "aa".
+						 */
+#ifdef RPR_DEBUG
+						elog(DEBUG1, "pattern[1]:%c pattern[2]:%c new[0]:%c new[1]:%c",
+							 pattern[1], pattern[2], new->data[0], new->data[1]);
+#endif
+						if (pattern[1] == new->data[0] &&
+							pattern[1] == new->data[1] &&
+							pattern[2] != '+' &&
+							pattern[1] != pattern[2])
+						{
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "discard this new data: %s",
+								 new->data);
+#endif
+							pfree(new->data);
+							pfree(new);
+							continue;
+						}
+
+						/* add new one to string set */
+						string_set_add(new_str_set, new);
+					}
+					else
+					{
+						/*
+						 * We are freezing this pattern string.  Since there's
+						 * no chance to expand the string further, we perform
+						 * pattern matching against the string. If it does not
+						 * match, we can discard it.
+						 */
+						len = do_pattern_match(pattern, old->data);
+
+						if (len <= 0)
+						{
+							/* no match. we can discard it */
+							continue;
+						}
+
+						else if (len <= resultlen)
+						{
+							/* shorter match. we can discard it */
+							continue;
+						}
+						else
+						{
+							/* match length is the longest so far */
+
+							int			new_index;
+
+							/* remember the longest match */
+							resultlen = len;
+
+							/* freeze the pattern string */
+							new = makeStringInfo();
+							enlargeStringInfo(new, old->len + 1);
+							appendStringInfoString(new, old->data);
+							/* add freezed mark */
+							appendStringInfoChar(new, FREEZED_CHAR);
+#ifdef RPR_DEBUG
+							elog(DEBUG1, "old_str: %s new_str: %s", old->data, new->data);
+#endif
+							string_set_add(new_str_set, new);
+
+							/*
+							 * Search new_str_set to find out freezed entries
+							 * that have shorter match length. Mark them as
+							 * "discard" so that they are discarded in the
+							 * next round.
+							 */
+
+							/* new_index_size should be the one before */
+							new_str_size =
+								string_set_get_size(new_str_set) - 1;
+
+							/* loop over new_str_set */
+							for (new_index = 0; new_index < new_str_size;
+								 new_index++)
+							{
+								char		new_last_char;
+								int			new_str_len;
+
+								new = string_set_get(new_str_set, new_index);
+								new_str_len = strlen(new->data);
+								if (new_str_len > 0)
+								{
+									new_last_char =
+										new->data[new_str_len - 1];
+									if (new_last_char == FREEZED_CHAR &&
+										(new_str_len - 1) <= len)
+									{
+										/*
+										 * mark this set to discard in the
+										 * next round
+										 */
+										appendStringInfoChar(new, DISCARD_CHAR);
+#ifdef RPR_DEBUG
+										elog(DEBUG1, "add discard char: %s", new->data);
+#endif
+									}
+								}
+							}
+						}
+					}
+				}
+			}
+			/* we no longer need old string set */
+			string_set_discard(old_str_set);
+		}
+	}
+
+	/*
+	 * Perform pattern matching to find out the longest match.
+	 */
+	new_str_size = string_set_get_size(new_str_set);
+#ifdef RPR_DEBUG
+	elog(DEBUG1, "new_str_size: %d", new_str_size);
+#endif
+	len = 0;
+	resultlen = 0;
+
+	for (index = 0; index < new_str_size; index++)
+	{
+		StringInfo	s;
+
+		s = string_set_get(new_str_set, index);
+		if (s == NULL)
+			continue;			/* no data */
+
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "target string: %s", s->data);
+#endif
+		len = do_pattern_match(pattern, s->data);
+		if (len > resultlen)
+		{
+			/* remember the longest match */
+			resultlen = len;
+
+			/*
+			 * If the size of result set is equal to the number of rows in the
+			 * set, we are done because it's not possible that the number of
+			 * matching rows exceeds the number of rows in the set.
+			 */
+			if (resultlen >= set_size)
+				break;
+		}
+	}
+
+	/* we no longer need new string set */
+	string_set_discard(new_str_set);
+
+	return resultlen;
+}
+
+/*
+ * do_pattern_match
+ * perform pattern match using pattern against encoded_str.
+ * returns matching number of rows if matching is succeeded.
+ * Otherwise returns 0.
+ */
+static
+int
+do_pattern_match(char *pattern, char *encoded_str)
+{
+	Datum		d;
+	text	   *res;
+	char	   *substr;
+	int			len = 0;
+	text	   *pattern_text,
+			   *encoded_str_text;
+
+	pattern_text = cstring_to_text(pattern);
+	encoded_str_text = cstring_to_text(encoded_str);
+
+	/*
+	 * We first perform pattern matching using regexp_instr, then call
+	 * textregexsubstr to get matched substring to know how long the matched
+	 * string is. That is the number of rows in the reduced window frame.  The
+	 * reason why we can't call textregexsubstr in the first place is, it
+	 * errors out if pattern does not match.
+	 */
+	if (DatumGetInt32(DirectFunctionCall2Coll(
+						  regexp_instr, DEFAULT_COLLATION_OID,
+						  PointerGetDatum(encoded_str_text),
+						  PointerGetDatum(pattern_text))))
+	{
+		d = DirectFunctionCall2Coll(textregexsubstr,
+									DEFAULT_COLLATION_OID,
+									PointerGetDatum(encoded_str_text),
+									PointerGetDatum(pattern_text));
+		if (d != 0)
+		{
+			res = DatumGetTextPP(d);
+			substr = text_to_cstring(res);
+			len = strlen(substr);
+			pfree(substr);
+		}
+	}
+	pfree(encoded_str_text);
+	pfree(pattern_text);
+
+	return len;
+}
+
+/*
+ * evaluate_pattern
+ * Evaluate expression associated with PATTERN variable vname.  current_pos is
+ * relative row position in a frame (starting from 0). If vname is evaluated
+ * to true, initial letters associated with vname is appended to
+ * encode_str. result is out paramater representing the expression evaluation
+ * result is true of false.
+ *---------
+ * Return values are:
+ * >=0: the last match absolute row position
+ * otherwise out of frame.
+ *---------
+ */
+static
+int64
+evaluate_pattern(WindowObject winobj, int64 current_pos,
+				 char *vname, StringInfo encoded_str, bool *result)
+{
+	WindowAggState *winstate = winobj->winstate;
+	ExprContext *econtext = winstate->ss.ps.ps_ExprContext;
+	ListCell   *lc1,
+			   *lc2,
+			   *lc3;
+	ExprState  *pat;
+	Datum		eval_result;
+	bool		out_of_frame = false;
+	bool		isnull;
+	TupleTableSlot *slot;
+
+	forthree(lc1, winstate->defineVariableList,
+			 lc2, winstate->defineClauseList,
+			 lc3, winstate->defineInitial)
+	{
+		char		initial;	/* initial letter associated with vname */
+		char	   *name = strVal(lfirst(lc1));
+
+		if (strcmp(vname, name))
+			continue;
+
+		initial = *(strVal(lfirst(lc3)));
+
+		/* set expression to evaluate */
+		pat = lfirst(lc2);
+
+		/* get current, previous and next tuples */
+		if (!get_slots(winobj, current_pos))
+		{
+			out_of_frame = true;
+		}
+		else
+		{
+			/* evaluate the expression */
+			eval_result = ExecEvalExpr(pat, econtext, &isnull);
+			if (isnull)
+			{
+				/* expression is NULL */
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "expression for %s is NULL at row: " INT64_FORMAT,
+					 vname, current_pos);
+#endif
+				*result = false;
+			}
+			else
+			{
+				if (!DatumGetBool(eval_result))
+				{
+					/* expression is false */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is false at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					*result = false;
+				}
+				else
+				{
+					/* expression is true */
+#ifdef RPR_DEBUG
+					elog(DEBUG1, "expression for %s is true at row: " INT64_FORMAT,
+						 vname, current_pos);
+#endif
+					appendStringInfoChar(encoded_str, initial);
+					*result = true;
+				}
+			}
+
+			slot = winstate->temp_slot_1;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->prev_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+			slot = winstate->next_slot;
+			if (slot != winstate->null_slot)
+				ExecClearTuple(slot);
+
+			break;
+		}
+
+		if (out_of_frame)
+		{
+			*result = false;
+			return -1;
+		}
+	}
+	return current_pos;
+}
+
+/*
+ * get_slots
+ * Get current, previous and next tuples.
+ * Returns false if current row is out of partition/full frame.
+ */
+static
+bool
+get_slots(WindowObject winobj, int64 current_pos)
+{
+	WindowAggState *winstate = winobj->winstate;
+	TupleTableSlot *slot;
+	int			ret;
+	ExprContext *econtext;
+
+	econtext = winstate->ss.ps.ps_ExprContext;
+
+	/* set up current row tuple slot */
+	slot = winstate->temp_slot_1;
+	if (!window_gettupleslot(winobj, current_pos, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of partition at:" INT64_FORMAT,
+			 current_pos);
+#endif
+		return false;
+	}
+	ret = row_is_in_frame(winstate, current_pos, slot);
+	if (ret <= 0)
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "current row is out of frame at: " INT64_FORMAT,
+			 current_pos);
+#endif
+		ExecClearTuple(slot);
+		return false;
+	}
+	econtext->ecxt_outertuple = slot;
+
+	/* for PREV */
+	if (current_pos > 0)
+	{
+		slot = winstate->prev_slot;
+		if (!window_gettupleslot(winobj, current_pos - 1, slot))
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "previous row is out of partition at: " INT64_FORMAT,
+				 current_pos - 1);
+#endif
+			econtext->ecxt_scantuple = winstate->null_slot;
+		}
+		else
+		{
+			ret = row_is_in_frame(winstate, current_pos - 1, slot);
+			if (ret <= 0)
+			{
+#ifdef RPR_DEBUG
+				elog(DEBUG1, "previous row is out of frame at: " INT64_FORMAT,
+					 current_pos - 1);
+#endif
+				ExecClearTuple(slot);
+				econtext->ecxt_scantuple = winstate->null_slot;
+			}
+			else
+			{
+				econtext->ecxt_scantuple = slot;
+			}
+		}
+	}
+	else
+		econtext->ecxt_scantuple = winstate->null_slot;
+
+	/* for NEXT */
+	slot = winstate->next_slot;
+	if (!window_gettupleslot(winobj, current_pos + 1, slot))
+	{
+#ifdef RPR_DEBUG
+		elog(DEBUG1, "next row is out of partiton at: " INT64_FORMAT,
+			 current_pos + 1);
+#endif
+		econtext->ecxt_innertuple = winstate->null_slot;
+	}
+	else
+	{
+		ret = row_is_in_frame(winstate, current_pos + 1, slot);
+		if (ret <= 0)
+		{
+#ifdef RPR_DEBUG
+			elog(DEBUG1, "next row is out of frame at: " INT64_FORMAT,
+				 current_pos + 1);
+#endif
+			ExecClearTuple(slot);
+			econtext->ecxt_innertuple = winstate->null_slot;
+		}
+		else
+			econtext->ecxt_innertuple = slot;
+	}
+	return true;
+}
+
+/*
+ * pattern_initial
+ * Return pattern variable initial character
+ * matching with pattern variable name vname.
+ * If not found, return 0.
+ */
+static
+char
+pattern_initial(WindowAggState *winstate, char *vname)
+{
+	char		initial;
+	char	   *name;
+	ListCell   *lc1,
+			   *lc2;
+
+	forboth(lc1, winstate->defineVariableList,
+			lc2, winstate->defineInitial)
+	{
+		name = strVal(lfirst(lc1)); /* DEFINE variable name */
+		initial = *(strVal(lfirst(lc2)));	/* DEFINE variable initial */
+
+
+		if (!strcmp(name, vname))
+			return initial;		/* found */
+	}
+	return 0;
+}
+
+/*
+ * string_set_init
+ * Create dynamic set of StringInfo.
+ */
+static
+StringSet * string_set_init(void)
+{
+/* Initial allocation size of str_set */
+#define STRING_SET_ALLOC_SIZE	1024
+
+	StringSet  *string_set;
+	Size		set_size;
+
+	string_set = palloc0(sizeof(StringSet));
+	string_set->set_index = 0;
+	set_size = STRING_SET_ALLOC_SIZE;
+	string_set->str_set = palloc(set_size * sizeof(StringInfo));
+	string_set->set_size = set_size;
+
+	return string_set;
+}
+
+/*
+ * string_set_add
+ * Add StringInfo str to StringSet string_set.
+ */
+static
+void
+string_set_add(StringSet * string_set, StringInfo str)
+{
+	Size		set_size;
+
+	set_size = string_set->set_size;
+	if (string_set->set_index >= set_size)
+	{
+		set_size *= 2;
+		string_set->str_set = repalloc(string_set->str_set,
+									   set_size * sizeof(StringInfo));
+		string_set->set_size = set_size;
+	}
+
+	string_set->str_set[string_set->set_index++] = str;
+
+	return;
+}
+
+/*
+ * string_set_get
+ * Returns StringInfo specified by index.
+ * If there's no data yet, returns NULL.
+ */
+static
+StringInfo
+string_set_get(StringSet * string_set, int index)
+{
+	/* no data? */
+	if (index == 0 && string_set->set_index == 0)
+		return NULL;
+
+	if (index < 0 || index >= string_set->set_index)
+		elog(ERROR, "invalid index: %d", index);
+
+	return string_set->str_set[index];
+}
+
+/*
+ * string_set_get_size
+ * Returns the size of StringSet.
+ */
+static
+int
+string_set_get_size(StringSet * string_set)
+{
+	return string_set->set_index;
+}
+
+/*
+ * string_set_discard
+ * Discard StringSet.
+ * All memory including StringSet itself is freed.
+ */
+static
+void
+string_set_discard(StringSet * string_set)
+{
+	int			i;
+
+	for (i = 0; i < string_set->set_index; i++)
+	{
+		StringInfo	str = string_set->str_set[i];
+
+		if (str)
+		{
+			pfree(str->data);
+			pfree(str);
+		}
+	}
+	pfree(string_set->str_set);
+	pfree(string_set);
+}
+
+/*
+ * variable_pos_init
+ * Create and initialize variable postion structure
+ */
+static
+VariablePos * variable_pos_init(void)
+{
+	VariablePos *variable_pos;
+
+	variable_pos = palloc(sizeof(VariablePos) * NUM_ALPHABETS);
+	MemSet(variable_pos, -1, sizeof(VariablePos) * NUM_ALPHABETS);
+	return variable_pos;
+}
+
+/*
+ * variable_pos_register
+ * Register pattern variable whose initial is initial into postion index.
+ * pos is position of initial.
+ * If pos is already registered, register it at next empty slot.
+ */
+static
+void
+variable_pos_register(VariablePos * variable_pos, char initial, int pos)
+{
+	int			index = initial - 'a';
+	int			slot;
+	int			i;
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	for (i = 0; i < NUM_ALPHABETS; i++)
+	{
+		slot = variable_pos[index].pos[i];
+		if (slot < 0)
+		{
+			/* empty slot found */
+			variable_pos[index].pos[i] = pos;
+			return;
+		}
+	}
+	elog(ERROR, "no empty slot for initial: %c", initial);
+}
+
+/*
+ * variable_pos_compare
+ * Returns true if initial1 can be followed by initial2
+ */
+static
+bool
+variable_pos_compare(VariablePos * variable_pos, char initial1, char initial2)
+{
+	int			index1,
+				index2;
+	int			pos1,
+				pos2;
+
+	for (index1 = 0;; index1++)
+	{
+		pos1 = variable_pos_fetch(variable_pos, initial1, index1);
+		if (pos1 < 0)
+			break;
+
+		for (index2 = 0;; index2++)
+		{
+			pos2 = variable_pos_fetch(variable_pos, initial2, index2);
+			if (pos2 < 0)
+				break;
+			if (pos1 <= pos2)
+				return true;
+		}
+	}
+	return false;
+}
+
+/*
+ * variable_pos_fetch
+ * Fetch position of pattern variable whose initial is initial, and whose index
+ * is index. If no postion was registered by initial, index, returns -1.
+ */
+static
+int
+variable_pos_fetch(VariablePos * variable_pos, char initial, int index)
+{
+	int			pos = initial - 'a';
+
+	if (pos < 0 || pos > NUM_ALPHABETS)
+		elog(ERROR, "initial is not valid char: %c", initial);
+
+	if (index < 0 || index > NUM_ALPHABETS)
+		elog(ERROR, "index is not valid: %d", index);
+
+	return variable_pos[pos].pos[index];
+}
+
+/*
+ * variable_pos_discard
+ * Discard VariablePos
+ */
+static
+void
+variable_pos_discard(VariablePos * variable_pos)
+{
+	pfree(variable_pos);
+}
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 473c61569f..92c528d38c 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,6 +13,9 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_collation_d.h"
+#include "executor/executor.h"
+#include "nodes/execnodes.h"
 #include "nodes/parsenodes.h"
 #include "nodes/supportnodes.h"
 #include "utils/fmgrprotos.h"
@@ -37,11 +40,19 @@ typedef struct
 	int64		remainder;		/* (total rows) % (bucket num) */
 } ntile_context;
 
+/*
+ * rpr process information.
+ * Used for AFTER MATCH SKIP PAST LAST ROW
+ */
+typedef struct SkipContext
+{
+	int64		pos;			/* last row absolute position */
+}			SkipContext;
+
 static bool rank_up(WindowObject winobj);
 static Datum leadlag_common(FunctionCallInfo fcinfo,
 							bool forward, bool withoffset, bool withdefault);
 
-
 /*
  * utility routine for *_rank functions.
  */
@@ -674,7 +685,7 @@ window_last_value(PG_FUNCTION_ARGS)
 	bool		isnull;
 
 	result = WinGetFuncArgInFrame(winobj, 0,
-								  0, WINDOW_SEEK_TAIL, true,
+								  0, WINDOW_SEEK_TAIL, false,
 								  &isnull, NULL);
 	if (isnull)
 		PG_RETURN_NULL();
@@ -714,3 +725,25 @@ window_nth_value(PG_FUNCTION_ARGS)
 
 	PG_RETURN_DATUM(result);
 }
+
+/*
+ * prev
+ * Dummy function to invoke RPR's navigation operator "PREV".
+ * This is *not* a window function.
+ */
+Datum
+window_prev(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
+
+/*
+ * next
+ * Dummy function to invoke RPR's navigation operation "NEXT".
+ * This is *not* a window function.
+ */
+Datum
+window_next(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_DATUM(PG_GETARG_DATUM(0));
+}
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 2a9f2105b1..595ef7502e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -10479,6 +10479,12 @@
 { oid => '3114', descr => 'fetch the Nth row value',
   proname => 'nth_value', prokind => 'w', prorettype => 'anyelement',
   proargtypes => 'anyelement int4', prosrc => 'window_nth_value' },
+{ oid => '6122', descr => 'previous value',
+  proname => 'prev', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_prev' },
+{ oid => '6123', descr => 'next value',
+  proname => 'next', provolatile => 's', prorettype => 'anyelement',
+  proargtypes => 'anyelement', prosrc => 'window_next' },
 
 # functions for range types
 { oid => '3832', descr => 'I/O',
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 8bc421e7c0..4dd9a17eca 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2554,6 +2554,11 @@ typedef enum WindowAggStatus
 									 * tuples during spool */
 } WindowAggStatus;
 
+#define	RF_NOT_DETERMINED	0
+#define	RF_FRAME_HEAD		1
+#define	RF_SKIPPED			2
+#define	RF_UNMATCHED		3
+
 typedef struct WindowAggState
 {
 	ScanState	ss;				/* its first field is NodeTag */
@@ -2602,6 +2607,19 @@ typedef struct WindowAggState
 	int64		groupheadpos;	/* current row's peer group head position */
 	int64		grouptailpos;	/* " " " " tail position (group end+1) */
 
+	/* these fields are used in Row pattern recognition: */
+	RPSkipTo	rpSkipTo;		/* Row Pattern Skip To type */
+	List	   *patternVariableList;	/* list of row pattern variables names
+										 * (list of String) */
+	List	   *patternRegexpList;	/* list of row pattern regular expressions
+									 * ('+' or ''. list of String) */
+	List	   *defineVariableList; /* list of row pattern definition
+									 * variables (list of String) */
+	List	   *defineClauseList;	/* expression for row pattern definition
+									 * search conditions ExprState list */
+	List	   *defineInitial;	/* list of row pattern definition variable
+								 * initials (list of String) */
+
 	MemoryContext partcontext;	/* context for partition-lifespan data */
 	MemoryContext aggcontext;	/* shared context for aggregate working data */
 	MemoryContext curaggcontext;	/* current aggregate's working data */
@@ -2638,6 +2656,18 @@ typedef struct WindowAggState
 	TupleTableSlot *agg_row_slot;
 	TupleTableSlot *temp_slot_1;
 	TupleTableSlot *temp_slot_2;
+
+	/* temporary slots for RPR */
+	TupleTableSlot *prev_slot;	/* PREV row navigation operator */
+	TupleTableSlot *next_slot;	/* NEXT row navigation operator */
+	TupleTableSlot *null_slot;	/* all NULL slot */
+
+	/*
+	 * Each byte corresponds to a row positioned at absolute its pos in
+	 * partition.  See above definition for RF_*
+	 */
+	char	   *reduced_frame_map;
+	int64		alloc_sz;		/* size of the map */
 } WindowAggState;
 
 /* ----------------
-- 
2.25.1


----Next_Part(Wed_May_15_09_02_03_2024_008)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="v19-0006-Row-pattern-recognition-patch-docs.patch"



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

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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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

* [PATCH v2 2/2] fixups
@ 2026-07-07 16:35 Álvaro Herrera <[email protected]>
  0 siblings, 0 replies; 132+ messages in thread

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

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

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


--op6mnexl7cn72cto--





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


end of thread, other threads:[~2026-07-07 16:35 UTC | newest]

Thread overview: 132+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2018-02-28 23:20 [PATCH v3 3/3] fixups Alvaro Herrera <[email protected]>
2023-05-16 13:42 [PATCH v10 3/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <[email protected]>
2023-05-16 13:42 [PATCH v9 2/3] Dedicated memory context for hash join spill buffers Jehan-Guillaume de Rorthais <[email protected]>
2024-05-14 23:26 [PATCH v19 5/8] Row pattern recognition patch (executor). Tatsuo Ishii <[email protected]>
2026-02-09 16:51 [PATCH v7 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>
2026-07-07 16:35 [PATCH v2 2/2] fixups Álvaro Herrera <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox