v8-0010-Add-pg_temp_index-global-temporary-catalog-table.patch
text/x-patch
Filename: v8-0010-Add-pg_temp_index-global-temporary-catalog-table.patch
Type: text/x-patch
Part: 9
Message:
Re: Global temporary tables
Patch
Format: format-patch
Series: patch v8-0010
Subject: Add pg_temp_index global temporary catalog table.
| File | + | − |
|---|---|---|
| contrib/tcn/tcn.c | 4 | 3 |
| src/backend/access/transam/xact.c | 5 | 4 |
| src/backend/catalog/global_temp.c | 33 | 6 |
| src/backend/catalog/index.c | 42 | 6 |
| src/backend/catalog/pg_temp_class.c | 493 | 66 |
| src/backend/commands/indexcmds.c | 30 | 11 |
| src/backend/commands/repack.c | 1 | 2 |
| src/backend/commands/tablecmds.c | 27 | 12 |
| src/backend/utils/cache/lsyscache.c | 2 | 2 |
| src/backend/utils/cache/relcache.c | 39 | 5 |
| src/bin/psql/describe.c | 62 | 18 |
| src/include/catalog/Makefile | 1 | 0 |
| src/include/catalog/meson.build | 1 | 0 |
| src/include/catalog/pg_temp_class.h | 8 | 1 |
| src/include/catalog/pg_temp_index.h | 66 | 0 |
| src/test/isolation/expected/global-temp.out | 81 | 0 |
| src/test/isolation/specs/global-temp.spec | 2 | 0 |
| src/test/regress/expected/global_temp.out | 106 | 1 |
| src/test/regress/expected/oidjoins.out | 1 | 0 |
| src/test/regress/sql/global_temp.sql | 34 | 0 |
| src/tools/pgindent/typedefs.list | 2 | 0 |
From f84e2e962a115f80fd1eebe35d88808a7ef9032b Mon Sep 17 00:00:00 2001
From: Dean Rasheed <dean.a.rasheed@gmail.com>
Date: Sat, 20 Jun 2026 09:37:58 +0100
Subject: [PATCH v8 10/10] Add pg_temp_index global temporary catalog table.
This just has indexrelid and indisvalid columns, allowing the valid
state of an index to session-specific. This is needed when one session
defines an index on a global temporary table, while another session is
using the table, and has already populated it. In all other cases,
pg_index.indexrelid and pg_temp_index.indexrelid are expected to be
the same.
---
contrib/tcn/tcn.c | 7 +-
src/backend/access/transam/xact.c | 9 +-
src/backend/catalog/global_temp.c | 39 +-
src/backend/catalog/index.c | 48 +-
src/backend/catalog/pg_temp_class.c | 559 +++++++++++++++++---
src/backend/commands/indexcmds.c | 41 +-
src/backend/commands/repack.c | 3 +-
src/backend/commands/tablecmds.c | 39 +-
src/backend/utils/cache/lsyscache.c | 4 +-
src/backend/utils/cache/relcache.c | 44 +-
src/bin/psql/describe.c | 80 ++-
src/include/catalog/Makefile | 1 +
src/include/catalog/meson.build | 1 +
src/include/catalog/pg_temp_class.h | 9 +-
src/include/catalog/pg_temp_index.h | 66 +++
src/test/isolation/expected/global-temp.out | 81 +++
src/test/isolation/specs/global-temp.spec | 2 +
src/test/regress/expected/global_temp.out | 107 +++-
src/test/regress/expected/oidjoins.out | 1 +
src/test/regress/sql/global_temp.sql | 34 ++
src/tools/pgindent/typedefs.list | 2 +
21 files changed, 1040 insertions(+), 137 deletions(-)
create mode 100644 src/include/catalog/pg_temp_index.h
diff --git a/contrib/tcn/tcn.c b/contrib/tcn/tcn.c
index 6b4bc7960d9..3201f438c4b 100644
--- a/contrib/tcn/tcn.c
+++ b/contrib/tcn/tcn.c
@@ -16,6 +16,7 @@
#include "postgres.h"
#include "access/htup_details.h"
+#include "catalog/pg_temp_class.h"
#include "commands/async.h"
#include "commands/trigger.h"
#include "executor/spi.h"
@@ -135,7 +136,7 @@ triggered_change_notification(PG_FUNCTION_ARGS)
HeapTuple indexTuple;
Form_pg_index index;
- indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
+ indexTuple = GetEffectivePgIndexTuple(indexoid);
if (!HeapTupleIsValid(indexTuple)) /* should not happen */
elog(ERROR, "cache lookup failed for index %u", indexoid);
index = (Form_pg_index) GETSTRUCT(indexTuple);
@@ -167,10 +168,10 @@ triggered_change_notification(PG_FUNCTION_ARGS)
Async_Notify(channel, payload.data);
}
- ReleaseSysCache(indexTuple);
+ heap_freetuple(indexTuple);
break;
}
- ReleaseSysCache(indexTuple);
+ heap_freetuple(indexTuple);
}
list_free(indexoidlist);
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index 7c631de9393..8fbbb425e2c 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -37,6 +37,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "catalog/storage.h"
#include "commands/async.h"
#include "commands/tablecmds.h"
@@ -1149,7 +1150,7 @@ CommandCounterIncrement(void)
(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
errmsg("cannot start commands during a parallel operation")));
- /* Flush out any pending inserts to pg_temp_class */
+ /* Flush out any pending pg_temp_class and pg_temp_index inserts */
PreCCI_PgTempClass();
currentCommandId += 1;
@@ -2364,7 +2365,7 @@ CommitTransaction(void)
*/
PreCommit_on_commit_actions();
- /* Flush out any pending inserts to pg_temp_class */
+ /* Flush out any pending pg_temp_class and pg_temp_index inserts */
PreCommit_PgTempClass();
/*
@@ -2640,7 +2641,7 @@ PrepareTransaction(void)
*/
PreCommit_on_commit_actions();
- /* Flush out any pending inserts to pg_temp_class */
+ /* Flush out any pending pg_temp_class and pg_temp_index inserts */
PreCommit_PgTempClass();
/*
@@ -5202,7 +5203,7 @@ CommitSubTransaction(void)
CallSubXactCallbacks(SUBXACT_EVENT_PRE_COMMIT_SUB, s->subTransactionId,
s->parent->subTransactionId);
- /* Flush out any pending inserts to pg_temp_class */
+ /* Flush out any pending pg_temp_class and pg_temp_index inserts */
PreSubCommit_PgTempClass();
/*
diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c
index 6cc89c59890..9abf1f23165 100644
--- a/src/backend/catalog/global_temp.c
+++ b/src/backend/catalog/global_temp.c
@@ -122,6 +122,7 @@ static bool eoxact_storage_list_overflowed = false;
typedef struct GtrUsageEntry
{
Oid relid; /* lookup key: OID of relation in use */
+ char relkind; /* relkind of the relation */
SubTransactionId started_subid; /* usage started in current xact */
SubTransactionId stopped_subid; /* usage ended with another subid set */
} GtrUsageEntry;
@@ -519,7 +520,7 @@ gtr_init_usage_tables(void)
* Note: This is intentionally idempotent.
*/
static void
-gtr_record_usage(Oid relid)
+gtr_record_usage(Oid relid, char relkind)
{
GtrUsageEntry *local_entry;
GtrSharedUsageKey key;
@@ -534,6 +535,9 @@ gtr_record_usage(Oid relid)
if (found)
return; /* already recorded, nothing to do */
+ /* Remember the relation's relkind */
+ local_entry->relkind = relkind;
+
/* Record the usage as starting in the current subtransaction */
local_entry->started_subid = GetCurrentSubTransactionId();
local_entry->stopped_subid = InvalidSubTransactionId;
@@ -973,14 +977,33 @@ GlobalTempRelationCreated(Relation relation)
{
/*
* If this is the first time we've used this relation in this session,
- * insert a pg_temp_class tuple for it, and update the usage hash tables.
+ * insert pg_temp_class and pg_temp_index tuples for it, and update the
+ * usage hash tables.
*/
if (gtr_local_usage == NULL ||
hash_search(gtr_local_usage,
&relation->rd_id, HASH_FIND, NULL) == NULL)
{
InsertPgTempClassTuple(relation);
- gtr_record_usage(relation->rd_id);
+
+ if (relation->rd_rel->relkind == RELKIND_INDEX ||
+ relation->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)
+ {
+ /*
+ * When creating a new index locally, relation->rd_index will be
+ * NULL here. Mark it as valid for now --- UpdateIndexRelation()
+ * will update it later, if it's not actually valid (e.g., CREATE
+ * INDEX ... ON ONLY ...). Otherwise, for an index created in
+ * another session, relation->rd_index->indisvalid will accurately
+ * reflect whether or not the index needs to be marked invalid
+ * locally (if our instance of the index's table is not empty).
+ */
+ InsertPgTempIndexTuple(relation->rd_id,
+ relation->rd_index == NULL ||
+ relation->rd_index->indisvalid);
+ }
+
+ gtr_record_usage(relation->rd_id, relation->rd_rel->relkind);
}
}
@@ -1011,8 +1034,12 @@ GlobalTempRelationDropped(Oid relid)
/* Flag the usage entry for eoxact cleanup */
EOXactUsageListAdd(relid);
- /* Delete it's pg_temp_class tuple */
+ /* Delete it's pg_temp_class and pg_temp_index tuples */
DeletePgTempClassTuple(relid);
+
+ if (entry->relkind == RELKIND_INDEX ||
+ entry->relkind == RELKIND_PARTITIONED_INDEX)
+ DeletePgTempIndexTuple(relid);
}
/* Forget any ON COMMIT action for the relation */
@@ -1142,7 +1169,7 @@ AtEOXact_GlobalTempRelation(bool isCommit)
}
}
- /* Perform any pg_temp_class processing */
+ /* Perform any pg_temp_class and pg_temp_index processing */
AtEOXact_PgTempClass(isCommit);
/* Now we're out of the transaction and can clear the lists */
@@ -1216,7 +1243,7 @@ AtEOSubXact_GlobalTempRelation(bool isCommit, SubTransactionId mySubid,
}
}
- /* Perform any pg_temp_class processing */
+ /* Perform any pg_temp_class and pg_temp_index processing */
AtEOSubXact_PgTempClass(isCommit, mySubid, parentSubid);
/* Don't reset the lists; we still need more cleanup later */
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 943de8a4fda..99123bde736 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -50,6 +50,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
@@ -121,7 +122,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid,
bool isexclusion,
bool immediate,
bool isvalid,
- bool isready);
+ bool isready,
+ char relpersistence);
static void index_update_stats(Relation rel,
bool isreindex,
bool hasindex,
@@ -574,7 +576,8 @@ UpdateIndexRelation(Oid indexoid,
bool isexclusion,
bool immediate,
bool isvalid,
- bool isready)
+ bool isready,
+ char relpersistence)
{
int2vector *indkey;
oidvector *indcollation;
@@ -675,6 +678,23 @@ UpdateIndexRelation(Oid indexoid,
*/
table_close(pg_index, RowExclusiveLock);
heap_freetuple(tuple);
+
+ /*
+ * For an index on a global temporary table, GlobalTempRelationCreated()
+ * will have inserted a pg_temp_index tuple with indisvalid = true. If
+ * the index is actually not valid, fix that now.
+ */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && !isvalid)
+ {
+ tuple = GetPgTempIndexTuple(indexoid);
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for global temp index %u", indexoid);
+
+ ((Form_pg_temp_index) GETSTRUCT(tuple))->indisvalid = isvalid;
+
+ UpdatePgTempIndexTuple(indexoid, tuple);
+ heap_freetuple(tuple);
+ }
}
@@ -1056,7 +1076,8 @@ index_create(Relation heapRelation,
isprimary, is_exclusion,
(constr_flags & INDEX_CONSTR_CREATE_DEFERRABLE) == 0,
!concurrent && !invalid,
- !concurrent);
+ !concurrent,
+ relpersistence);
/*
* Register relcache invalidation on the indexes' heap relation, to
@@ -3583,6 +3604,9 @@ index_set_state_flags(Oid indexId, IndexStateFlagsAction action)
HeapTuple indexTuple;
Form_pg_index indexForm;
+ /* This is not expected to be a global temporary index */
+ Assert(!rel_is_global_temp(indexId));
+
/* Open pg_index and fetch a writable copy of the index's tuple */
pg_index = table_open(IndexRelationId, RowExclusiveLock);
@@ -3942,18 +3966,26 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
{
Relation pg_index;
HeapTuple indexTuple;
+ HeapTuple temp_indexTuple;
Form_pg_index indexForm;
+ Form_pg_temp_index temp_indexForm;
bool index_bad;
+ /*
+ * For a global temporary index, we update indisvalid in both pg_index
+ * and pg_temp_index, so that the change applies to this session and
+ * all future sessions.
+ */
pg_index = table_open(IndexRelationId, RowExclusiveLock);
- indexTuple = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(indexId));
+ indexTuple = GetPgIndexAndPgTempIndexTuples(indexId, &temp_indexTuple,
+ true);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexId);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
+ temp_indexForm = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_indexTuple);
- index_bad = (!indexForm->indisvalid ||
+ index_bad = (!GetEffective_indisvalid(indexForm, temp_indexForm) ||
!indexForm->indisready ||
!indexForm->indislive);
if (index_bad ||
@@ -3964,9 +3996,13 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
else if (index_bad)
indexForm->indcheckxmin = true;
indexForm->indisvalid = true;
+ if (temp_indexForm != NULL)
+ temp_indexForm->indisvalid = true;
indexForm->indisready = true;
indexForm->indislive = true;
CatalogTupleUpdate(pg_index, &indexTuple->t_self, indexTuple);
+ if (HeapTupleIsValid(temp_indexTuple))
+ UpdatePgTempIndexTuple(indexId, temp_indexTuple);
/*
* Invalidate the relcache for the table, so that after we commit
diff --git a/src/backend/catalog/pg_temp_class.c b/src/backend/catalog/pg_temp_class.c
index 4f294515479..6386e30bfa8 100644
--- a/src/backend/catalog/pg_temp_class.c
+++ b/src/backend/catalog/pg_temp_class.c
@@ -1,7 +1,8 @@
/*-------------------------------------------------------------------------
*
* pg_temp_class.c
- * routines to support manipulation of the pg_temp_class relation
+ * routines to support manipulation of the pg_temp_class and
+ * pg_temp_index relations
*
* The pg_temp_class system catalog table is a global temporary table that
* stores local overrides to various fields from the pg_class table for the
@@ -38,9 +39,14 @@
* with the database, if they have been flushed), until the end of the
* transaction, when they are discarded.
*
- * NB: All reads and writes to pg_temp_class by backend code must go
- * through the functions defined here (though user SQL queries may read it
- * normally).
+ * Likewise pg_temp_index is the global temporary system catalog table that
+ * stores local overrides to fields (actually just indisvalid) in the
+ * pg_index table for the duration of the current session. It requires
+ * similar treatment, so for convenience we manage that here too.
+ *
+ * NB: All reads and writes to pg_temp_class and pg_temp_index by backend
+ * code must go through the functions defined here (though user SQL queries
+ * may read them normally).
*
* Copyright (c) 2026, PostgreSQL Global Development Group
*
@@ -58,29 +64,35 @@
#include "access/xact.h"
#include "catalog/indexing.h"
#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "miscadmin.h"
#include "storage/proc.h"
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
+#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
/*
- * Have we opened and initialized pg_temp_class in this session?
+ * Have we opened and initialized pg_temp_class and pg_temp_index in this
+ * session?
*/
static bool pg_temp_class_opened = false;
+static bool pg_temp_index_opened = false;
/*
- * Subtransaction ID in which pg_temp_class was opened, if it was opened in
- * the current transaction, else zero.
+ * Subtransaction IDs in which pg_temp_class and pg_temp_index were opened, if
+ * they were opened in the current transaction, else zero.
*/
static SubTransactionId pg_temp_class_subid = InvalidSubTransactionId;
+static SubTransactionId pg_temp_index_subid = InvalidSubTransactionId;
-/* Cached copy of the pg_temp_class tuple descriptor */
+/* Cached copies of the pg_temp_class and pg_temp_index tuple descriptors */
static TupleDesc pg_temp_class_tupdesc = NULL;
+static TupleDesc pg_temp_index_tupdesc = NULL;
/*
- * Pending inserts to pg_temp_class.
+ * Pending inserts to pg_temp_class and pg_temp_index.
*
* Each pending insert entry may be flushed to the database at any point
* during the transaction which added it, but the entry is kept up-to-date
@@ -101,7 +113,10 @@ typedef struct PendingInsert
struct PendingInsert *prev; /* previous version, for subxact rollback */
} PendingInsert;
-static HTAB *pending_inserts = NULL;
+static HTAB *pending_class_inserts = NULL;
+static HTAB *pending_index_inserts = NULL;
+
+/* Do we have any pending inserts of either kind that need flushing? */
static bool have_inserts_to_flush = false;
/* Memory context for all tuples pending insert */
@@ -136,27 +151,62 @@ open_pg_temp_class(LOCKMODE lockmode)
}
/*
- * init_pending_inserts_hashtable
+ * open_pg_temp_index
*
- * Initialize the pending inserts hashtable, if not already done.
+ * Open pg_temp_index and make a note of the subtranscation ID, if this is
+ * the first time opening it.
+ */
+static Relation
+open_pg_temp_index(LOCKMODE lockmode)
+{
+ Relation pg_temp_index;
+
+ pg_temp_index = table_open(TempIndexRelationId, lockmode);
+ if (!pg_temp_index_opened)
+ {
+ pg_temp_index_opened = true;
+ pg_temp_index_subid = GetCurrentSubTransactionId();
+ }
+ return pg_temp_index;
+}
+
+/*
+ * init_pending_inserts_hashtables
+ *
+ * Initialize the pending inserts hashtables, if not already done.
*/
static void
-init_pending_inserts_hashtable(void)
+init_pending_inserts_hashtables(void)
{
- if (pending_inserts == NULL)
+ if (pending_class_inserts == NULL)
+ {
+ HASHCTL ctl;
+
+ /* Create the hash table for pending pg_temp_class inserts */
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(PendingInsert);
+
+ pending_class_inserts = hash_create("Pending pg_temp_class inserts",
+ 128, &ctl, HASH_ELEM | HASH_BLOBS);
+ }
+
+ if (pending_index_inserts == NULL)
{
HASHCTL ctl;
- /* Create the hash table */
+ /* Create the hash table for pending pg_temp_index inserts */
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(PendingInsert);
- pending_inserts = hash_create("Pending pg_temp_class inserts",
- 128, &ctl, HASH_ELEM | HASH_BLOBS);
+ pending_index_inserts = hash_create("Pending pg_temp_index inserts",
+ 128, &ctl, HASH_ELEM | HASH_BLOBS);
+ }
- /* Create a separate memory context for all tuples in it */
+ if (pending_inserts_tupctx == NULL)
+ {
+ /* Create a separate memory context for all pending tuples */
pending_inserts_tupctx = AllocSetContextCreate(TopMemoryContext,
- "Pending pg_temp_class tuples",
+ "Pending pg_temp_class/index tuples",
ALLOCSET_DEFAULT_SIZES);
}
}
@@ -215,6 +265,39 @@ get_pg_temp_class_tupdesc(void)
return pg_temp_class_tupdesc;
}
+/*
+ * get_pg_temp_index_tupdesc
+ *
+ * Returns the tuple descriptor for pg_temp_index.
+ */
+static TupleDesc
+get_pg_temp_index_tupdesc(void)
+{
+ /* Build the tuple descriptor the first time through */
+ if (pg_temp_index_tupdesc == NULL)
+ {
+ MemoryContext oldcontext;
+ TupleDesc tupdesc;
+
+ oldcontext = MemoryContextSwitchTo(TopMemoryContext);
+
+ tupdesc = CreateTemplateTupleDesc(Natts_pg_temp_index);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_index_indexrelid,
+ "indexrelid", OIDOID, -1, 0);
+ TupleDescInitEntry(tupdesc,
+ (AttrNumber) Anum_pg_temp_index_indisvalid,
+ "indisvalid", BOOLOID, -1, 0);
+ TupleDescFinalize(tupdesc);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Cache it for all future use */
+ pg_temp_index_tupdesc = tupdesc;
+ }
+ return pg_temp_index_tupdesc;
+}
+
/*
* heap_form_pg_temp_class_tuple
*
@@ -241,6 +324,23 @@ heap_form_pg_temp_class_tuple(Relation rel)
return heap_form_tuple(get_pg_temp_class_tupdesc(), values, nulls);
}
+/*
+ * heap_form_pg_temp_index_tuple
+ *
+ * Create a pg_temp_index tuple for the specified index relation.
+ */
+static HeapTuple
+heap_form_pg_temp_index_tuple(Oid indexrelid, bool indisvalid)
+{
+ Datum values[Natts_pg_temp_index];
+ bool nulls[Natts_pg_temp_index] = {0};
+
+ values[Anum_pg_temp_index_indexrelid - 1] = ObjectIdGetDatum(indexrelid);
+ values[Anum_pg_temp_index_indisvalid - 1] = BoolGetDatum(indisvalid);
+
+ return heap_form_tuple(get_pg_temp_index_tupdesc(), values, nulls);
+}
+
/*
* prepare_pending_insert_for_edit
*
@@ -274,57 +374,98 @@ prepare_pending_insert_for_edit(PendingInsert *entry)
}
/*
- * flush_pending_pg_temp_class_inserts
+ * flush_pending_inserts
*
- * Flush any pending inserts to pg_temp_class.
+ * Flush any pending inserts to pg_temp_class and pg_temp_index.
*/
static void
-flush_pending_pg_temp_class_inserts(void)
+flush_pending_inserts(void)
{
Relation pg_temp_class;
- CatalogIndexState indstate;
+ Relation pg_temp_index;
+ CatalogIndexState class_indstate;
+ CatalogIndexState index_indstate;
HASH_SEQ_STATUS status;
PendingInsert *entry;
+ /*
+ * Open pg_temp_class, pg_temp_index, and their indexes. Note that this
+ * might be the first time these have been opened in the current session,
+ * so we open them all first, in case they lead to more pending inserts.
+ */
pg_temp_class = open_pg_temp_class(RowExclusiveLock);
+ pg_temp_index = open_pg_temp_index(RowExclusiveLock);
+
+ class_indstate = CatalogOpenIndexes(pg_temp_class);
+ index_indstate = CatalogOpenIndexes(pg_temp_index);
/*
- * Flush all pending inserts, not already flushed or deleted.
+ * Flush all pending pg_temp_class inserts, not already flushed or
+ * deleted.
*/
- indstate = CatalogOpenIndexes(pg_temp_class);
- hash_seq_init(&status, pending_inserts);
+ hash_seq_init(&status, pending_class_inserts);
+ while ((entry = hash_seq_search(&status)) != NULL)
+ {
+ if (!entry->flushed && !entry->deleted)
+ {
+ CatalogTupleInsertWithInfo(pg_temp_class, entry->tuple,
+ class_indstate);
+
+ /* Update the entry, saving a copy for rollback, if necessary */
+ prepare_pending_insert_for_edit(entry);
+ entry->flushed = true;
+ }
+ }
+
+ /* And likewise for pending pg_temp_index inserts */
+ hash_seq_init(&status, pending_index_inserts);
while ((entry = hash_seq_search(&status)) != NULL)
{
if (!entry->flushed && !entry->deleted)
{
- CatalogTupleInsertWithInfo(pg_temp_class, entry->tuple, indstate);
+ CatalogTupleInsertWithInfo(pg_temp_index, entry->tuple,
+ index_indstate);
/* Update the entry, saving a copy for rollback, if necessary */
prepare_pending_insert_for_edit(entry);
entry->flushed = true;
}
}
- CatalogCloseIndexes(indstate);
+
+ /* Tidy up */
+ CatalogCloseIndexes(class_indstate);
+ CatalogCloseIndexes(index_indstate);
table_close(pg_temp_class, RowExclusiveLock);
+ table_close(pg_temp_index, RowExclusiveLock);
have_inserts_to_flush = false;
}
/*
- * discard_pending_pg_temp_class_inserts
+ * discard_pending_inserts
*
- * Discard any pending inserts to pg_temp_class.
+ * Discard any pending inserts to pg_temp_class and pg_temp_index.
*/
static void
-discard_pending_pg_temp_class_inserts(void)
+discard_pending_inserts(void)
{
- /* Just blow away the hash table and tuple memory context */
- hash_destroy(pending_inserts);
- MemoryContextDelete(pending_inserts_tupctx);
-
- pending_inserts = NULL;
- pending_inserts_tupctx = NULL;
+ /* Just blow away the hash tables and tuple memory context */
+ if (pending_class_inserts != NULL)
+ {
+ hash_destroy(pending_class_inserts);
+ pending_class_inserts = NULL;
+ }
+ if (pending_index_inserts != NULL)
+ {
+ hash_destroy(pending_index_inserts);
+ pending_index_inserts = NULL;
+ }
+ if (pending_inserts_tupctx != NULL)
+ {
+ MemoryContextDelete(pending_inserts_tupctx);
+ pending_inserts_tupctx = NULL;
+ }
have_inserts_to_flush = false;
}
@@ -342,9 +483,9 @@ GetPgTempClassTuple(Oid relid)
PendingInsert *entry = NULL;
/* If there is a pending insert for this relation, return that */
- if (pending_inserts != NULL)
+ if (pending_class_inserts != NULL)
{
- entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+ entry = hash_search(pending_class_inserts, &relid, HASH_FIND, NULL);
if (entry != NULL)
return entry->deleted ? NULL : heap_copytuple(entry->tuple);
}
@@ -357,6 +498,35 @@ GetPgTempClassTuple(Oid relid)
return SearchSysCacheCopy1(TEMPRELOID, ObjectIdGetDatum(relid));
}
+/*
+ * GetPgTempIndexTuple
+ *
+ * Get the pg_temp_index tuple for a global temporary index relation.
+ *
+ * Returns NULL if the tuple could not be found. Otherwise, the tuple
+ * returned should be freed with heap_freetuple().
+ */
+HeapTuple
+GetPgTempIndexTuple(Oid indexrelid)
+{
+ PendingInsert *entry;
+
+ /* Is there a pending insert for this relation? */
+ if (pending_index_inserts != NULL)
+ {
+ entry = hash_search(pending_index_inserts, &indexrelid, HASH_FIND, NULL);
+ if (entry != NULL)
+ return entry->deleted ? NULL : heap_copytuple(entry->tuple);
+ }
+
+ /* If we haven't opened pg_temp_index yet, it must be empty */
+ if (!pg_temp_index_opened)
+ return NULL;
+
+ /* Otherwise, fetch a copy of the tuple from the database */
+ return SearchSysCacheCopy1(TEMPINDEXRELID, ObjectIdGetDatum(indexrelid));
+}
+
/*
* InsertPgTempClassTuple
*
@@ -383,9 +553,9 @@ InsertPgTempClassTuple(Relation rel)
* taking care to allocate the tuple in the long-term memory context for
* pending insert tuples.
*/
- init_pending_inserts_hashtable();
+ init_pending_inserts_hashtables();
- entry = hash_search(pending_inserts, &relid, HASH_ENTER, &found);
+ entry = hash_search(pending_class_inserts, &relid, HASH_ENTER, &found);
if (found)
/* Should never try to re-insert the same relid */
elog(ERROR, "pg_temp_class tuple for relation %u already exists", relid);
@@ -401,6 +571,49 @@ InsertPgTempClassTuple(Relation rel)
have_inserts_to_flush = true;
}
+/*
+ * InsertPgTempIndexTuple
+ *
+ * Insert a new pg_temp_index tuple for a global temporary index relation.
+ *
+ * This is called when a global temporary index relation is created or
+ * accessed for the first time in a session.
+ *
+ * Note: The new tuple is not written to the database unless and until
+ * CommandCounterIncrement() is called for a non-read-only command, or the
+ * (sub)transaction is committed. However, the new tuple *is* visible to all
+ * the functions defined here.
+ */
+void
+InsertPgTempIndexTuple(Oid indexrelid, bool indisvalid)
+{
+ PendingInsert *entry;
+ bool found;
+ MemoryContext oldcontext;
+
+ /*
+ * Add a new tuple for the relation to the pending inserts hash table,
+ * taking care to allocate the tuple in the long-term memory context for
+ * pending insert tuples.
+ */
+ init_pending_inserts_hashtables();
+
+ entry = hash_search(pending_index_inserts, &indexrelid, HASH_ENTER, &found);
+ if (found)
+ /* Should never try to re-insert the same indexrelid */
+ elog(ERROR, "pg_temp_index tuple for index %u already exists", indexrelid);
+
+ oldcontext = MemoryContextSwitchTo(pending_inserts_tupctx);
+ entry->tuple = heap_form_pg_temp_index_tuple(indexrelid, indisvalid);
+ entry->flushed = false;
+ entry->deleted = false;
+ entry->subid = GetCurrentSubTransactionId();
+ entry->prev = NULL;
+ MemoryContextSwitchTo(oldcontext);
+
+ have_inserts_to_flush = true;
+}
+
/*
* UpdatePgTempClassTuple
*
@@ -413,11 +626,11 @@ UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple)
HeapTuple oldtuple;
/* Is there a pending insert for this relation? */
- if (pending_inserts != NULL)
+ if (pending_class_inserts != NULL)
{
PendingInsert *entry;
- entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+ entry = hash_search(pending_class_inserts, &relid, HASH_FIND, NULL);
if (entry != NULL)
{
Form_pg_temp_class old_form;
@@ -479,11 +692,11 @@ UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple)
void *inplace_state;
/* Is there a pending insert for this relation? */
- if (pending_inserts != NULL)
+ if (pending_class_inserts != NULL)
{
PendingInsert *entry;
- entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+ entry = hash_search(pending_class_inserts, &relid, HASH_FIND, NULL);
if (entry != NULL)
{
Form_pg_temp_class old_form;
@@ -538,6 +751,69 @@ UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple)
table_close(pg_temp_class, RowExclusiveLock);
}
+/*
+ * UpdatePgTempIndexTuple
+ *
+ * Update the pg_temp_index tuple for a global temporary index relation.
+ */
+void
+UpdatePgTempIndexTuple(Oid indexrelid, HeapTuple newtuple)
+{
+ Relation pg_temp_index;
+ HeapTuple oldtuple;
+
+ /* Is there a pending insert for this relation? */
+ if (pending_index_inserts != NULL)
+ {
+ PendingInsert *entry;
+
+ entry = hash_search(pending_index_inserts, &indexrelid, HASH_FIND, NULL);
+ if (entry != NULL)
+ {
+ Form_pg_temp_index old_form;
+ Form_pg_temp_index new_form;
+
+ /* Should not have been deleted */
+ if (entry->deleted)
+ elog(ERROR,
+ "pending insert for global temp index %u was deleted",
+ indexrelid);
+
+ /* Update the entry, saving a copy for rollback, if necessary */
+ prepare_pending_insert_for_edit(entry);
+ old_form = (Form_pg_temp_index) GETSTRUCT(entry->tuple);
+ new_form = (Form_pg_temp_index) GETSTRUCT(newtuple);
+ old_form->indisvalid = new_form->indisvalid;
+
+ /*
+ * If it has not yet been flushed to the database, do so now. This
+ * is important, because the caller might be relying on a relcache
+ * invalidation being triggered.
+ */
+ if (!entry->flushed)
+ {
+ pg_temp_index = open_pg_temp_index(RowExclusiveLock);
+ CatalogTupleInsert(pg_temp_index, newtuple);
+ table_close(pg_temp_index, RowExclusiveLock);
+ entry->flushed = true;
+ return;
+ }
+ }
+ }
+
+ /* Update the tuple in the database */
+ pg_temp_index = open_pg_temp_index(RowExclusiveLock);
+
+ oldtuple = SearchSysCache1(TEMPINDEXRELID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(oldtuple))
+ elog(ERROR, "cache lookup failed for global temp index %u", indexrelid);
+
+ CatalogTupleUpdate(pg_temp_index, &oldtuple->t_self, newtuple);
+ ReleaseSysCache(oldtuple);
+
+ table_close(pg_temp_index, RowExclusiveLock);
+}
+
/*
* DeletePgTempClassTuple
*
@@ -550,11 +826,11 @@ DeletePgTempClassTuple(Oid relid)
HeapTuple oldtuple;
/* Is there a pending insert for this relation? */
- if (pending_inserts != NULL)
+ if (pending_class_inserts != NULL)
{
PendingInsert *entry;
- entry = hash_search(pending_inserts, &relid, HASH_FIND, NULL);
+ entry = hash_search(pending_class_inserts, &relid, HASH_FIND, NULL);
if (entry != NULL)
{
/* Should not have already been deleted */
@@ -589,6 +865,57 @@ DeletePgTempClassTuple(Oid relid)
table_close(pg_temp_class, RowExclusiveLock);
}
+/*
+ * DeletePgTempIndexTuple
+ *
+ * Delete the pg_temp_index tuple for a global temporary index relation.
+ */
+void
+DeletePgTempIndexTuple(Oid indexrelid)
+{
+ Relation pg_temp_index;
+ HeapTuple oldtuple;
+
+ /* Is there a pending insert for this relation? */
+ if (pending_index_inserts != NULL)
+ {
+ PendingInsert *entry;
+
+ entry = hash_search(pending_index_inserts, &indexrelid, HASH_FIND, NULL);
+ if (entry != NULL)
+ {
+ /* Should not have already been deleted */
+ if (entry->deleted)
+ elog(ERROR,
+ "pending insert for global temp relation %u already deleted",
+ indexrelid);
+
+ /* Update the entry, saving a copy for rollback, if necessary */
+ prepare_pending_insert_for_edit(entry);
+ entry->deleted = true;
+
+ /*
+ * If it has been flushed to the database, need to delete it there
+ * too. Otherwise, we're done.
+ */
+ if (!entry->flushed)
+ return;
+ }
+ }
+
+ /* Delete the tuple from the database */
+ pg_temp_index = open_pg_temp_index(RowExclusiveLock);
+
+ oldtuple = SearchSysCache1(TEMPINDEXRELID, ObjectIdGetDatum(indexrelid));
+ if (!HeapTupleIsValid(oldtuple))
+ elog(ERROR, "cache lookup failed for global temp index %u", indexrelid);
+
+ CatalogTupleDelete(pg_temp_index, &oldtuple->t_self);
+ ReleaseSysCache(oldtuple);
+
+ table_close(pg_temp_index, RowExclusiveLock);
+}
+
/*
* GetPgClassAndPgTempClassTuples
*
@@ -631,6 +958,39 @@ GetPgClassAndPgTempClassTuples(Oid relid, bool lock_tuple,
return tuple;
}
+/*
+ * GetPgIndexAndPgTempIndexTuples
+ *
+ * Get the pg_index tuple for an index relation, and if it's a global
+ * temporary index relation, also get the corresponding pg_temp_index tuple,
+ * if present.
+ *
+ * Returns NULL if the pg_index tuple could not be found. Otherwise, the
+ * tuple(s) returned should be freed with heap_freetuple().
+ */
+HeapTuple
+GetPgIndexAndPgTempIndexTuples(Oid indexrelid, HeapTuple *temp_tuple,
+ bool check_temp)
+{
+ HeapTuple tuple;
+
+ /* Get a copy of the pg_index tuple */
+ tuple = SearchSysCacheCopy1(INDEXRELID, ObjectIdGetDatum(indexrelid));
+
+ if (HeapTupleIsValid(tuple) &&
+ rel_is_global_temp(((Form_pg_index) GETSTRUCT(tuple))->indexrelid))
+ {
+ /* Get the pg_temp_index tuple, and check it exists, if requested */
+ *temp_tuple = GetPgTempIndexTuple(indexrelid);
+ if (check_temp && !HeapTupleIsValid(*temp_tuple))
+ elog(ERROR, "cache lookup failed for global temp index %u", indexrelid);
+ }
+ else
+ *temp_tuple = NULL;
+
+ return tuple;
+}
+
/*
* GetEffectivePgClassTuple
*
@@ -672,6 +1032,47 @@ GetEffectivePgClassTuple(Oid relid)
return tuple;
}
+/*
+ * GetEffectivePgIndexTuple
+ *
+ * Get the effective pg_index tuple for an index relation.
+ *
+ * This will fetch the pg_index tuple for the relation and then, if it's a
+ * global temporary relation, fetch the corresponding pg_temp_index tuple and
+ * use the values in it to override the corresponding values in the pg_index
+ * tuple (currently just indisvalid). Thus, the result represents the
+ * effective state of the index relation in this session.
+ *
+ * For a global temporary index relation that has not yet been opened in this
+ * session, there will be no pg_temp_index tuple, and the pg_index tuple will
+ * be returned unchanged.
+ *
+ * Returns NULL if the pg_index tuple could not be found. Otherwise, the
+ * tuple returned should be freed with heap_freetuple().
+ */
+HeapTuple
+GetEffectivePgIndexTuple(Oid indexrelid)
+{
+ HeapTuple tuple;
+ HeapTuple temp_tuple;
+ Form_pg_index indexform;
+ Form_pg_temp_index temp_indexform;
+
+ /*
+ * Get the pg_index and pg_temp_index tuples. If we have the latter, use
+ * it to update the former.
+ */
+ tuple = GetPgIndexAndPgTempIndexTuples(indexrelid, &temp_tuple, false);
+
+ if (HeapTupleIsValid(tuple) && HeapTupleIsValid(temp_tuple))
+ {
+ indexform = (Form_pg_index) GETSTRUCT(tuple);
+ temp_indexform = (Form_pg_temp_index) GETSTRUCT(temp_tuple);
+ indexform->indisvalid = temp_indexform->indisvalid;
+ }
+ return tuple;
+}
+
/*
* UpdateTempFrozenXids
*
@@ -701,10 +1102,10 @@ UpdateTempFrozenXids(void)
min_relfrozenxid = InvalidTransactionId;
min_relminmxid = InvalidMultiXactId;
- /* Processing any pending inserts */
- if (pending_inserts != NULL)
+ /* Processing any pending pg_temp_class inserts */
+ if (pending_class_inserts != NULL)
{
- hash_seq_init(&status, pending_inserts);
+ hash_seq_init(&status, pending_class_inserts);
while ((entry = hash_seq_search(&status)) != NULL)
{
temp_form = (Form_pg_temp_class) GETSTRUCT(entry->tuple);
@@ -785,7 +1186,7 @@ void
PreCCI_PgTempClass(void)
{
if (have_inserts_to_flush)
- flush_pending_pg_temp_class_inserts();
+ flush_pending_inserts();
}
/*
@@ -797,7 +1198,7 @@ void
PreCommit_PgTempClass(void)
{
if (have_inserts_to_flush)
- flush_pending_pg_temp_class_inserts();
+ flush_pending_inserts();
}
/*
@@ -809,7 +1210,7 @@ void
PreSubCommit_PgTempClass(void)
{
if (have_inserts_to_flush)
- flush_pending_pg_temp_class_inserts();
+ flush_pending_inserts();
}
/*
@@ -828,13 +1229,17 @@ AtEOXact_PgTempClass(bool isCommit)
pg_temp_class_opened = false;
pg_temp_class_subid = InvalidSubTransactionId;
+ /* Likewise for pg_temp_index */
+ if (!isCommit && pg_temp_index_subid != InvalidSubTransactionId)
+ pg_temp_index_opened = false;
+ pg_temp_index_subid = InvalidSubTransactionId;
+
/*
- * Blow away the pending inserts hash table. On commit, there should be
+ * Blow away the pending inserts hash tables. On commit, there should be
* no remaining inserts to flush, but on rollback, there may be.
*/
Assert(!(isCommit && have_inserts_to_flush));
- if (pending_inserts != NULL)
- discard_pending_pg_temp_class_inserts();
+ discard_pending_inserts();
/*
* On commit, save any new tempfrozenxid and tempminmxid values to our
@@ -854,8 +1259,8 @@ AtEOXact_PgTempClass(bool isCommit)
* Sub-transaction commit or abort processing for a single pending insert.
*/
static void
-AtEOSubXact_PendingInsert(PendingInsert *entry, bool isCommit,
- SubTransactionId mySubid,
+AtEOSubXact_PendingInsert(HTAB *pending_inserts, PendingInsert *entry,
+ bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid)
{
/*
@@ -902,6 +1307,9 @@ void
AtEOSubXact_PgTempClass(bool isCommit, SubTransactionId mySubid,
SubTransactionId parentSubid)
{
+ HASH_SEQ_STATUS status;
+ PendingInsert *entry;
+
/*
* Was pg_temp_class first opened and initialized in the current
* subtransaction?
@@ -920,18 +1328,37 @@ AtEOSubXact_PgTempClass(bool isCommit, SubTransactionId mySubid,
}
}
- /*
- * Tidy up any pending inserts.
- */
- if (pending_inserts != NULL)
+ /* Likewise for pg_temp_index */
+ if (pg_temp_index_subid == mySubid)
{
- HASH_SEQ_STATUS status;
- PendingInsert *entry;
+ if (isCommit)
+ pg_temp_index_subid = parentSubid;
+ else
+ {
+ pg_temp_index_opened = false;
+ pg_temp_index_subid = InvalidSubTransactionId;
+ }
+ }
+
+ /* Tidy up any pending pg_temp_class inserts */
+ if (pending_class_inserts != NULL)
+ {
+ hash_seq_init(&status, pending_class_inserts);
+ while ((entry = hash_seq_search(&status)) != NULL)
+ {
+ AtEOSubXact_PendingInsert(pending_class_inserts, entry,
+ isCommit, mySubid, parentSubid);
+ }
+ }
- hash_seq_init(&status, pending_inserts);
+ /* Likewise for pg_temp_index */
+ if (pending_index_inserts != NULL)
+ {
+ hash_seq_init(&status, pending_index_inserts);
while ((entry = hash_seq_search(&status)) != NULL)
{
- AtEOSubXact_PendingInsert(entry, isCommit, mySubid, parentSubid);
+ AtEOSubXact_PendingInsert(pending_index_inserts, entry,
+ isCommit, mySubid, parentSubid);
}
}
}
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 8baac58ef81..beb90ba09a7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -37,6 +37,8 @@
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_tablespace.h"
+#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "catalog/pg_type.h"
#include "commands/comment.h"
#include "commands/defrem.h"
@@ -269,7 +271,7 @@ CheckIndexCompatible(Oid oldId,
0, NULL);
/* Get the soon-obsolete pg_index tuple. */
- tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(oldId));
+ tuple = GetEffectivePgIndexTuple(oldId);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u", oldId);
indexForm = (Form_pg_index) GETSTRUCT(tuple);
@@ -282,7 +284,7 @@ CheckIndexCompatible(Oid oldId,
heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) &&
indexForm->indisvalid))
{
- ReleaseSysCache(tuple);
+ heap_freetuple(tuple);
return false;
}
@@ -299,7 +301,7 @@ CheckIndexCompatible(Oid oldId,
ret = (memcmp(old_indclass->values, opclassIds, old_natts * sizeof(Oid)) == 0 &&
memcmp(old_indcollation->values, collationIds, old_natts * sizeof(Oid)) == 0);
- ReleaseSysCache(tuple);
+ heap_freetuple(tuple);
if (!ret)
return false;
@@ -1581,19 +1583,36 @@ DefineIndex(ParseState *pstate,
{
Relation pg_index = table_open(IndexRelationId, RowExclusiveLock);
HeapTuple tup,
- newtup;
+ temp_tup;
+ Form_pg_index form;
+ Form_pg_temp_index temp_form;
- tup = SearchSysCache1(INDEXRELID,
- ObjectIdGetDatum(indexRelationId));
+ /*
+ * For a global temporary index, we update indisvalid in both
+ * pg_index and pg_temp_index, so that the change applies to
+ * this session and all future sessions.
+ */
+ tup = GetPgIndexAndPgTempIndexTuples(indexRelationId,
+ &temp_tup, true);
if (!HeapTupleIsValid(tup))
elog(ERROR, "cache lookup failed for index %u",
indexRelationId);
- newtup = heap_copytuple(tup);
- ((Form_pg_index) GETSTRUCT(newtup))->indisvalid = false;
- CatalogTupleUpdate(pg_index, &tup->t_self, newtup);
- ReleaseSysCache(tup);
+ form = (Form_pg_index) GETSTRUCT(tup);
+ temp_form = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_tup);
+
+ form->indisvalid = false;
+ if (temp_form != NULL)
+ temp_form->indisvalid = false;
+
+ CatalogTupleUpdate(pg_index, &tup->t_self, tup);
+ if (HeapTupleIsValid(temp_tup))
+ {
+ UpdatePgTempIndexTuple(indexRelationId, temp_tup);
+ heap_freetuple(temp_tup);
+ }
+
+ heap_freetuple(tup);
table_close(pg_index, RowExclusiveLock);
- heap_freetuple(newtup);
/*
* CCI here to make this update visible, in case this recurses
diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index 0c704a531b2..2ec0bcaca29 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -868,8 +868,7 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
{
Oid thisIndexOid = lfirst_oid(index);
- indexTuple = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(thisIndexOid));
+ indexTuple = GetEffectivePgIndexTuple(thisIndexOid);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", thisIndexOid);
indexForm = (Form_pg_index) GETSTRUCT(indexTuple);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index d3394fffd61..4dff3969097 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -55,6 +55,7 @@
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_type.h"
#include "catalog/storage.h"
@@ -1859,7 +1860,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
Form_pg_index indexform;
bool indisvalid;
- locTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relOid));
+ locTuple = GetEffectivePgIndexTuple(relOid);
if (!HeapTupleIsValid(locTuple))
{
ReleaseSysCache(tuple);
@@ -1868,7 +1869,7 @@ RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, Oid oldRelOid,
indexform = (Form_pg_index) GETSTRUCT(locTuple);
indisvalid = indexform->indisvalid;
- ReleaseSysCache(locTuple);
+ heap_freetuple(locTuple);
/* Mark object as being an invalid index of system catalogs */
if (!indisvalid)
@@ -14004,7 +14005,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
{
Oid indexoid = lfirst_oid(indexoidscan);
- indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
+ indexTuple = GetEffectivePgIndexTuple(indexoid);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
@@ -14024,7 +14025,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
*indexOid = indexoid;
break;
}
- ReleaseSysCache(indexTuple);
+ heap_freetuple(indexTuple);
}
list_free(indexoidlist);
@@ -14062,7 +14063,7 @@ transformFkeyGetPrimaryKey(Relation pkrel, Oid *indexOid,
*pk_has_without_overlaps = indexStruct->indisexclusion;
- ReleaseSysCache(indexTuple);
+ heap_freetuple(indexTuple);
return i;
}
@@ -14125,7 +14126,7 @@ transformFkeyCheckAttrs(Relation pkrel,
Form_pg_index indexStruct;
indexoid = lfirst_oid(indexoidscan);
- indexTuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(indexoid));
+ indexTuple = GetEffectivePgIndexTuple(indexoid);
if (!HeapTupleIsValid(indexTuple))
elog(ERROR, "cache lookup failed for index %u", indexoid);
indexStruct = (Form_pg_index) GETSTRUCT(indexTuple);
@@ -14201,7 +14202,7 @@ transformFkeyCheckAttrs(Relation pkrel,
if (found)
*pk_has_without_overlaps = indexStruct->indisexclusion;
}
- ReleaseSysCache(indexTuple);
+ heap_freetuple(indexTuple);
if (found)
break;
}
@@ -22489,14 +22490,13 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
HeapTuple indTup;
Form_pg_index indexForm;
- indTup = SearchSysCache1(INDEXRELID,
- ObjectIdGetDatum(inhForm->inhrelid));
+ indTup = GetEffectivePgIndexTuple(inhForm->inhrelid);
if (!HeapTupleIsValid(indTup))
elog(ERROR, "cache lookup failed for index %u", inhForm->inhrelid);
indexForm = (Form_pg_index) GETSTRUCT(indTup);
if (indexForm->indisvalid)
tuples += 1;
- ReleaseSysCache(indTup);
+ heap_freetuple(indTup);
}
/* Done with pg_inherits */
@@ -22511,20 +22511,35 @@ validatePartitionedIndex(Relation partedIdx, Relation partedTbl)
{
Relation idxRel;
HeapTuple indTup;
+ HeapTuple temp_indTup;
Form_pg_index indexForm;
+ Form_pg_temp_index temp_indexForm;
+ /*
+ * For a global temporary index, we update indisvalid in both pg_index
+ * and pg_temp_index, so that the change applies to this session and
+ * all future sessions.
+ */
idxRel = table_open(IndexRelationId, RowExclusiveLock);
- indTup = SearchSysCacheCopy1(INDEXRELID,
- ObjectIdGetDatum(RelationGetRelid(partedIdx)));
+ indTup = GetPgIndexAndPgTempIndexTuples(RelationGetRelid(partedIdx),
+ &temp_indTup, true);
if (!HeapTupleIsValid(indTup))
elog(ERROR, "cache lookup failed for index %u",
RelationGetRelid(partedIdx));
indexForm = (Form_pg_index) GETSTRUCT(indTup);
+ temp_indexForm = (Form_pg_temp_index) GETSTRUCT_SAFE(temp_indTup);
indexForm->indisvalid = true;
+ if (temp_indexForm != NULL)
+ temp_indexForm->indisvalid = true;
updated = true;
CatalogTupleUpdate(idxRel, &indTup->t_self, indTup);
+ if (HeapTupleIsValid(temp_indTup))
+ {
+ UpdatePgTempIndexTuple(RelationGetRelid(partedIdx), temp_indTup);
+ heap_freetuple(temp_indTup);
+ }
table_close(idxRel, RowExclusiveLock);
heap_freetuple(indTup);
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ecc34f07e36..d35b6c6ce12 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -3965,13 +3965,13 @@ get_index_isvalid(Oid index_oid)
HeapTuple tuple;
Form_pg_index rd_index;
- tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
+ tuple = GetEffectivePgIndexTuple(index_oid);
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u", index_oid);
rd_index = (Form_pg_index) GETSTRUCT(tuple);
isvalid = rd_index->indisvalid;
- ReleaseSysCache(tuple);
+ heap_freetuple(tuple);
return isvalid;
}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ea08c2428f..fd46606928a 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -63,6 +63,7 @@
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_temp_class.h"
+#include "catalog/pg_temp_index.h"
#include "catalog/pg_temp_statistic.h"
#include "catalog/pg_temp_statistic_ext_data.h"
#include "catalog/pg_trigger.h"
@@ -1531,6 +1532,23 @@ RelationInitIndexAccessInfo(Relation relation)
MemoryContextSwitchTo(oldcontext);
ReleaseSysCache(tuple);
+ /*
+ * For global temporary indexes, update indisvalid from pg_temp_index.
+ * This won't work for system catalog indexes, because pg_temp_index may
+ * not be loaded at this point, but they shouldn't be invalid anyway.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) && !IsCatalogRelation(relation))
+ {
+ tuple = GetPgTempIndexTuple(RelationGetRelid(relation));
+ if (HeapTupleIsValid(tuple))
+ {
+ Form_pg_temp_index temp_form = (Form_pg_temp_index) GETSTRUCT(tuple);
+
+ relation->rd_index->indisvalid = temp_form->indisvalid;
+ heap_freetuple(tuple);
+ }
+ }
+
/*
* Look up the index's access method, save the OID of its handler function
*/
@@ -2441,8 +2459,7 @@ RelationReloadIndexInfo(Relation relation)
HeapTuple tuple;
Form_pg_index index;
- tuple = SearchSysCache1(INDEXRELID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
+ tuple = GetEffectivePgIndexTuple(RelationGetRelid(relation));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for index %u",
RelationGetRelid(relation));
@@ -2470,7 +2487,7 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderSetXmin(relation->rd_indextuple->t_data,
HeapTupleHeaderGetXmin(tuple->t_data));
- ReleaseSysCache(tuple);
+ heap_freetuple(tuple);
}
/* Okay, now it's valid again */
@@ -5032,6 +5049,8 @@ RelationGetIndexList(Relation relation)
while (HeapTupleIsValid(htup = systable_getnext(indscan)))
{
Form_pg_index index = (Form_pg_index) GETSTRUCT(htup);
+ HeapTuple temp_htup;
+ bool indisvalid;
/*
* Ignore any indexes that are currently being dropped. This will
@@ -5055,6 +5074,21 @@ RelationGetIndexList(Relation relation)
!heap_attisnull(htup, Anum_pg_index_indpred, NULL))
continue;
+ /*
+ * Global temporary indexes may override indexisvalid locally.
+ */
+ indisvalid = index->indisvalid;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ temp_htup = GetPgTempIndexTuple(index->indexrelid);
+
+ if (HeapTupleIsValid(temp_htup))
+ {
+ indisvalid = ((Form_pg_temp_index) GETSTRUCT(temp_htup))->indisvalid;
+ heap_freetuple(temp_htup);
+ }
+ }
+
/*
* Remember primary key index, if any. For regular tables we do this
* only if the index is valid; but for partitioned tables, then we do
@@ -5066,7 +5100,7 @@ RelationGetIndexList(Relation relation)
* partitioned tables.
*/
if (index->indisprimary &&
- (index->indisvalid ||
+ (indisvalid ||
relation->rd_rel->relkind == RELKIND_PARTITIONED_TABLE))
{
pkeyIndex = index->indexrelid;
@@ -5076,7 +5110,7 @@ RelationGetIndexList(Relation relation)
if (!index->indimmediate)
continue;
- if (!index->indisvalid)
+ if (!indisvalid)
continue;
/* remember explicitly chosen replica index */
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 8ce4decb91e..3e9c2b85517 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -2406,6 +2406,20 @@ describeOneTableDetails(const char *schemaname,
char *indamname = PQgetvalue(result, 0, 8);
char *indtable = PQgetvalue(result, 0, 9);
char *indpred = PQgetvalue(result, 0, 10);
+ PGresult *temp_result = NULL;
+
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf, "/* %s */\n", _("Get temp index details"));
+ appendPQExpBuffer(&buf,
+ "SELECT i.indisvalid\n"
+ "FROM pg_catalog.pg_temp_index i\n"
+ "WHERE i.indexrelid = '%s'", oid);
+
+ temp_result = PSQLexec(buf.data);
+ if (temp_result && PQntuples(temp_result) == 1)
+ indisvalid = PQgetvalue(temp_result, 0, 0);
+ }
if (strcmp(indisprimary, "t") == 0)
printfPQExpBuffer(&tmpbuf, _("primary key, "));
@@ -2450,6 +2464,8 @@ describeOneTableDetails(const char *schemaname,
if (tableinfo.relkind == RELKIND_INDEX)
add_tablespace_footer(&cont, tableinfo.relkind,
tableinfo.tablespace, true);
+
+ PQclear(temp_result);
}
PQclear(result);
@@ -2482,6 +2498,7 @@ describeOneTableDetails(const char *schemaname,
appendPQExpBufferStr(&buf, ", con.conperiod");
else
appendPQExpBufferStr(&buf, ", false AS conperiod");
+ appendPQExpBufferStr(&buf, ", i.indexrelid");
appendPQExpBuffer(&buf,
"\nFROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i\n"
" LEFT JOIN pg_catalog.pg_constraint con ON (conrelid = i.indrelid AND conindid = i.indexrelid AND contype IN ("
@@ -2502,67 +2519,94 @@ describeOneTableDetails(const char *schemaname,
printTableAddFooter(&cont, _("Indexes:"));
for (i = 0; i < tuples; i++)
{
+ char *indname = PQgetvalue(result, i, 0);
+ char *indisprimary = PQgetvalue(result, i, 1);
+ char *indisunique = PQgetvalue(result, i, 2);
+ char *indisclustered = PQgetvalue(result, i, 3);
+ char *indisvalid = PQgetvalue(result, i, 4);
+ char *indexdef = PQgetvalue(result, i, 5);
+ char *constraintdef = PQgetvalue(result, i, 6);
+ char *contype = PQgetvalue(result, i, 7);
+ char *condeferrable = PQgetvalue(result, i, 8);
+ char *condeferred = PQgetvalue(result, i, 9);
+ char *indisreplident = PQgetvalue(result, i, 10);
+ char *reltablespace = PQgetvalue(result, i, 11);
+ char *conperiod = PQgetvalue(result, i, 12);
+ char *indexrelid = PQgetvalue(result, i, 13);
+ PGresult *temp_result = NULL;
+
+ if (pset.sversion >= 200000)
+ {
+ printfPQExpBuffer(&buf, "/* %s */\n", _("Get temp index details"));
+ appendPQExpBuffer(&buf,
+ "SELECT i.indisvalid\n"
+ "FROM pg_catalog.pg_temp_index i\n"
+ "WHERE i.indexrelid = '%s'", indexrelid);
+
+ temp_result = PSQLexec(buf.data);
+ if (temp_result && PQntuples(temp_result) == 1)
+ indisvalid = PQgetvalue(temp_result, 0, 0);
+ }
+
/* untranslated index name */
- printfPQExpBuffer(&buf, " \"%s\"",
- PQgetvalue(result, i, 0));
+ printfPQExpBuffer(&buf, " \"%s\"", indname);
/*
* If exclusion constraint or PK/UNIQUE constraint WITHOUT
* OVERLAPS, print the constraintdef
*/
- if (strcmp(PQgetvalue(result, i, 7), "x") == 0 ||
- strcmp(PQgetvalue(result, i, 12), "t") == 0)
+ if (strcmp(contype, "x") == 0 ||
+ strcmp(conperiod, "t") == 0)
{
- appendPQExpBuffer(&buf, " %s",
- PQgetvalue(result, i, 6));
+ appendPQExpBuffer(&buf, " %s", constraintdef);
}
else
{
- const char *indexdef;
- const char *usingpos;
+ char *usingpos;
/* Label as primary key or unique (but not both) */
- if (strcmp(PQgetvalue(result, i, 1), "t") == 0)
+ if (strcmp(indisprimary, "t") == 0)
appendPQExpBufferStr(&buf, " PRIMARY KEY,");
- else if (strcmp(PQgetvalue(result, i, 2), "t") == 0)
+ else if (strcmp(indisunique, "t") == 0)
{
- if (strcmp(PQgetvalue(result, i, 7), "u") == 0)
+ if (strcmp(contype, "u") == 0)
appendPQExpBufferStr(&buf, " UNIQUE CONSTRAINT,");
else
appendPQExpBufferStr(&buf, " UNIQUE,");
}
/* Everything after "USING" is echoed verbatim */
- indexdef = PQgetvalue(result, i, 5);
usingpos = strstr(indexdef, " USING ");
if (usingpos)
indexdef = usingpos + 7;
appendPQExpBuffer(&buf, " %s", indexdef);
/* Need these for deferrable PK/UNIQUE indexes */
- if (strcmp(PQgetvalue(result, i, 8), "t") == 0)
+ if (strcmp(condeferrable, "t") == 0)
appendPQExpBufferStr(&buf, " DEFERRABLE");
- if (strcmp(PQgetvalue(result, i, 9), "t") == 0)
+ if (strcmp(condeferred, "t") == 0)
appendPQExpBufferStr(&buf, " INITIALLY DEFERRED");
}
/* Add these for all cases */
- if (strcmp(PQgetvalue(result, i, 3), "t") == 0)
+ if (strcmp(indisclustered, "t") == 0)
appendPQExpBufferStr(&buf, " CLUSTER");
- if (strcmp(PQgetvalue(result, i, 4), "t") != 0)
+ if (strcmp(indisvalid, "t") != 0)
appendPQExpBufferStr(&buf, " INVALID");
- if (strcmp(PQgetvalue(result, i, 10), "t") == 0)
+ if (strcmp(indisreplident, "t") == 0)
appendPQExpBufferStr(&buf, " REPLICA IDENTITY");
printTableAddFooter(&cont, buf.data);
/* Print tablespace of the index on the same line */
add_tablespace_footer(&cont, RELKIND_INDEX,
- atooid(PQgetvalue(result, i, 11)),
+ atooid(reltablespace),
false);
+
+ PQclear(temp_result);
}
}
PQclear(result);
diff --git a/src/include/catalog/Makefile b/src/include/catalog/Makefile
index f60a6454df2..71ea60228d0 100644
--- a/src/include/catalog/Makefile
+++ b/src/include/catalog/Makefile
@@ -88,6 +88,7 @@ CATALOG_HEADERS := \
pg_propgraph_label_property.h \
pg_propgraph_property.h \
pg_temp_class.h \
+ pg_temp_index.h \
pg_temp_statistic.h \
pg_temp_statistic_ext_data.h
diff --git a/src/include/catalog/meson.build b/src/include/catalog/meson.build
index 7599731de7d..d6ea84a2d46 100644
--- a/src/include/catalog/meson.build
+++ b/src/include/catalog/meson.build
@@ -75,6 +75,7 @@ catalog_headers = [
'pg_propgraph_label_property.h',
'pg_propgraph_property.h',
'pg_temp_class.h',
+ 'pg_temp_index.h',
'pg_temp_statistic.h',
'pg_temp_statistic_ext_data.h',
]
diff --git a/src/include/catalog/pg_temp_class.h b/src/include/catalog/pg_temp_class.h
index a4629b12c9d..ba577001225 100644
--- a/src/include/catalog/pg_temp_class.h
+++ b/src/include/catalog/pg_temp_class.h
@@ -369,20 +369,27 @@ SetEffective_relminmxid(Form_pg_class cf, Form_pg_temp_class tf,
extern HeapTuple GetPgTempClassTuple(Oid relid);
+extern HeapTuple GetPgTempIndexTuple(Oid indexrelid);
extern void InsertPgTempClassTuple(Relation rel);
+extern void InsertPgTempIndexTuple(Oid indexrelid, bool indisvalid);
extern void UpdatePgTempClassTuple(Oid relid, HeapTuple newtuple);
-
extern void UpdatePgTempClassTupleInPlace(Oid relid, HeapTuple newtuple);
+extern void UpdatePgTempIndexTuple(Oid indexrelid, HeapTuple newtuple);
extern void DeletePgTempClassTuple(Oid relid);
+extern void DeletePgTempIndexTuple(Oid indexrelid);
extern HeapTuple GetPgClassAndPgTempClassTuples(Oid relid, bool lock_tuple,
HeapTuple *temp_tuple,
bool check_temp);
+extern HeapTuple GetPgIndexAndPgTempIndexTuples(Oid indexrelid,
+ HeapTuple *temp_tuple,
+ bool check_temp);
extern HeapTuple GetEffectivePgClassTuple(Oid relid);
+extern HeapTuple GetEffectivePgIndexTuple(Oid indexrelid);
extern void UpdateTempFrozenXids(void);
diff --git a/src/include/catalog/pg_temp_index.h b/src/include/catalog/pg_temp_index.h
new file mode 100644
index 00000000000..ce79bf0e2c5
--- /dev/null
+++ b/src/include/catalog/pg_temp_index.h
@@ -0,0 +1,66 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_temp_index.h
+ * definition of the "temporary index" system catalog (pg_temp_index)
+ *
+ * This is a global temporary system catalog table storing session-specific
+ * information about temporary indexes. Currently, it is only used for global
+ * temporary indexes. The attributes (currently just indisvalid) are a subset
+ * of those from pg_index, and their values take precedence over the values
+ * from pg_index.
+ *
+ * Portions Copyright (c) 2026, PostgreSQL Global Development Group
+ *
+ * src/include/catalog/pg_index.h
+ *
+ * NOTES
+ * The Catalog.pm module reads this file and derives schema
+ * information.
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PG_TEMP_INDEX_H
+#define PG_TEMP_INDEX_H
+
+#include "access/genam.h"
+#include "catalog/genbki.h"
+#include "catalog/pg_index.h"
+#include "catalog/pg_temp_index_d.h" /* IWYU pragma: export */
+
+/* ----------------
+ * pg_temp_index definition. cpp turns this into
+ * typedef struct FormData_pg_temp_index.
+ * ----------------
+ */
+BEGIN_CATALOG_STRUCT
+
+CATALOG(pg_temp_index,8092,TempIndexRelationId) BKI_TEMP_RELATION
+{
+ Oid indexrelid BKI_LOOKUP(pg_class); /* OID of the index */
+ bool indisvalid; /* is this index valid for use by queries? */
+} FormData_pg_temp_index;
+
+END_CATALOG_STRUCT
+
+/* ----------------
+ * Form_pg_temp_index corresponds to a pointer to a tuple with
+ * the format of pg_temp_index relation.
+ * ----------------
+ */
+typedef FormData_pg_temp_index *Form_pg_temp_index;
+
+DECLARE_UNIQUE_INDEX_PKEY(pg_temp_index_indexrelid_index, 8093, TempIndexRelidIndexId, pg_temp_index, btree(indexrelid oid_ops));
+
+MAKE_SYSCACHE(TEMPINDEXRELID, pg_temp_index_indexrelid_index, 64);
+
+/*
+ * Get the effective value of indisvalid from pg_index and pg_temp_index tuple
+ * data. The value from pg_temp_index (if present) takes precedence.
+ */
+static inline bool
+GetEffective_indisvalid(Form_pg_index f, Form_pg_temp_index tf)
+{
+ return tf != NULL ? tf->indisvalid : f->indisvalid;
+}
+
+#endif /* PG_TEMP_INDEX_H */
diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out
index bbaa768f479..b0f632779b6 100644
--- a/src/test/isolation/expected/global-temp.out
+++ b/src/test/isolation/expected/global-temp.out
@@ -438,6 +438,87 @@ key|val|seq
(1 row)
+starting permutation: ins1 ins2 idx1 sel1_idx sel2_idx analyze2 sel2_idx reidx2 sel2_idx
+step ins1: INSERT INTO tmp VALUES (1, 's1');
+step ins2: INSERT INTO tmp VALUES (1, 's2');
+step idx1: CREATE INDEX tmp_val_idx ON tmp(val);
+step sel1_idx:
+ SET enable_seqscan = off;
+ SET enable_bitmapscan = off;
+ EXPLAIN (COSTS OFF)
+ SELECT * FROM tmp WHERE val = 's1';
+ SELECT * FROM tmp WHERE val = 's1';
+
+QUERY PLAN
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+ Index Cond: (val = 's1'::text)
+(2 rows)
+
+key|val|seq
+---+---+---
+ 1|s1 | 1
+(1 row)
+
+step sel2_idx:
+ SET enable_seqscan = off;
+ SET enable_bitmapscan = off;
+ EXPLAIN (COSTS OFF)
+ SELECT * FROM tmp WHERE val = 's2';
+ SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN
+----------------------------
+Seq Scan on tmp
+ Disabled: true
+ Filter: (val = 's2'::text)
+(3 rows)
+
+key|val|seq
+---+---+---
+ 1|s2 | 1
+(1 row)
+
+step analyze2: ANALYZE tmp;
+step sel2_idx:
+ SET enable_seqscan = off;
+ SET enable_bitmapscan = off;
+ EXPLAIN (COSTS OFF)
+ SELECT * FROM tmp WHERE val = 's2';
+ SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN
+----------------------------
+Seq Scan on tmp
+ Disabled: true
+ Filter: (val = 's2'::text)
+(3 rows)
+
+key|val|seq
+---+---+---
+ 1|s2 | 1
+(1 row)
+
+step reidx2: REINDEX INDEX tmp_val_idx;
+step sel2_idx:
+ SET enable_seqscan = off;
+ SET enable_bitmapscan = off;
+ EXPLAIN (COSTS OFF)
+ SELECT * FROM tmp WHERE val = 's2';
+ SELECT * FROM tmp WHERE val = 's2';
+
+QUERY PLAN
+-----------------------------------
+Index Scan using tmp_val_idx on tmp
+ Index Cond: (val = 's2'::text)
+(2 rows)
+
+key|val|seq
+---+---+---
+ 1|s2 | 1
+(1 row)
+
+
starting permutation: ins1 ins2 t2 sel1 sel2 ins2 t1 sel1 sel2 ins1 t2 sel1 sel2
step ins1: INSERT INTO tmp VALUES (1, 's1');
step ins2: INSERT INTO tmp VALUES (1, 's2');
diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec
index a57fcfd3e53..b3b207acb9d 100644
--- a/src/test/isolation/specs/global-temp.spec
+++ b/src/test/isolation/specs/global-temp.spec
@@ -85,6 +85,7 @@ step get_tblspace2 {
LEFT JOIN pg_tablespace s2 ON s2.oid = t.reltablespace
WHERE c.relname = 'tmp';
}
+step analyze2 { ANALYZE tmp; }
# Create test tablespace for remaining tests
permutation create_tblspace list_tblspaces
@@ -114,6 +115,7 @@ permutation create1dr ins1_2 ins2_2 drop1 create1dr ins1_2 ins2_2 drop1
permutation ins1 idx1 sel1_idx ins2 sel2_idx
permutation ins1 ins2 idx1 sel1_idx sel2_idx
permutation ins1 ins2 idx1 sel1_idx sel2_idx reidx2 sel2_idx
+permutation ins1 ins2 idx1 sel1_idx sel2_idx analyze2 sel2_idx reidx2 sel2_idx
# Test local TRUNCATE
permutation ins1 ins2 t2 sel1 sel2 ins2 t1 sel1 sel2 ins1 t2 sel1 sel2
diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out
index 10519ceee21..693d12eaad4 100644
--- a/src/test/regress/expected/global_temp.out
+++ b/src/test/regress/expected/global_temp.out
@@ -227,6 +227,109 @@ SELECT * FROM tmp2 ORDER BY a;
2
(1 row)
+DROP TABLE tmp2;
+-- Test partitioned index validity
+CREATE GLOBAL TEMP TABLE tmp2 (a int) PARTITION BY LIST (a);
+CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1);
+CREATE INDEX tmp2_a_idx ON tmp2 (a);
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+ relname | global_valid | local_valid
+---------------+--------------+-------------
+ tmp2_a_idx | t | t
+ tmp2_p1_a_idx | t | t
+(2 rows)
+
+\d tmp2
+Global temporary partitioned table "global_temp_tests.tmp2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST (a)
+Indexes:
+ "tmp2_a_idx" btree (a)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d tmp2_a_idx
+Partitioned index "global_temp_tests.tmp2_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "global_temp_tests.tmp2"
+Number of partitions: 1 (Use \d+ to list them.)
+
+DROP INDEX tmp2_a_idx;
+CREATE INDEX tmp2_a_idx ON ONLY tmp2 (a);
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+ relname | global_valid | local_valid
+------------+--------------+-------------
+ tmp2_a_idx | f | f
+(1 row)
+
+\d tmp2
+Global temporary partitioned table "global_temp_tests.tmp2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST (a)
+Indexes:
+ "tmp2_a_idx" btree (a) INVALID
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d tmp2_a_idx
+Partitioned index "global_temp_tests.tmp2_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "global_temp_tests.tmp2", invalid
+Number of partitions: 0
+
+CREATE INDEX tmp2_p1_a_idx ON tmp2_p1 (a);
+ALTER INDEX tmp2_a_idx ATTACH PARTITION tmp2_p1_a_idx;
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+ relname | global_valid | local_valid
+---------------+--------------+-------------
+ tmp2_a_idx | t | t
+ tmp2_p1_a_idx | t | t
+(2 rows)
+
+\d tmp2
+Global temporary partitioned table "global_temp_tests.tmp2"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+---------
+ a | integer | | |
+Partition key: LIST (a)
+Indexes:
+ "tmp2_a_idx" btree (a)
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d tmp2_a_idx
+Partitioned index "global_temp_tests.tmp2_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+btree, for table "global_temp_tests.tmp2"
+Number of partitions: 1 (Use \d+ to list them.)
+
+\d tmp2_p1_a_idx
+Index "global_temp_tests.tmp2_p1_a_idx"
+ Column | Type | Key? | Definition
+--------+---------+------+------------
+ a | integer | yes | a
+Partition of: tmp2_a_idx
+btree, for table "global_temp_tests.tmp2_p1"
+
DROP TABLE tmp2;
-- Test ALTER TABLE with rewrite
CREATE GLOBAL TEMP TABLE tmp2 (a int);
@@ -510,10 +613,12 @@ SELECT oid::regclass FROM pg_temp_class ORDER BY 1;
pg_temp_class_oid_index
pg_temp_statistic
pg_temp_statistic_relid_att_inh_index
+ pg_temp_index
+ pg_temp_index_indexrelid_index
tmp1_c_seq
tmp1
tmp1_pkey
-(7 rows)
+(9 rows)
SELECT * FROM tmp1;
a | b | c
diff --git a/src/test/regress/expected/oidjoins.out b/src/test/regress/expected/oidjoins.out
index bccae052a15..2b4af8452e6 100644
--- a/src/test/regress/expected/oidjoins.out
+++ b/src/test/regress/expected/oidjoins.out
@@ -287,6 +287,7 @@ NOTICE: checking pg_propgraph_property {pgptypid} => pg_type {oid}
NOTICE: checking pg_propgraph_property {pgpcollation} => pg_collation {oid}
NOTICE: checking pg_temp_class {oid} => pg_class {oid}
NOTICE: checking pg_temp_class {reltablespace} => pg_tablespace {oid}
+NOTICE: checking pg_temp_index {indexrelid} => pg_class {oid}
NOTICE: checking pg_temp_statistic {starelid} => pg_class {oid}
NOTICE: checking pg_temp_statistic {staop1} => pg_operator {oid}
NOTICE: checking pg_temp_statistic {staop2} => pg_operator {oid}
diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql
index 4272824e894..3f887d3d369 100644
--- a/src/test/regress/sql/global_temp.sql
+++ b/src/test/regress/sql/global_temp.sql
@@ -129,6 +129,40 @@ SET search_path = global_temp_tests;
SELECT * FROM tmp2 ORDER BY a;
DROP TABLE tmp2;
+-- Test partitioned index validity
+CREATE GLOBAL TEMP TABLE tmp2 (a int) PARTITION BY LIST (a);
+CREATE GLOBAL TEMP TABLE tmp2_p1 PARTITION OF tmp2 FOR VALUES IN (1);
+CREATE INDEX tmp2_a_idx ON tmp2 (a);
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+\d tmp2
+\d tmp2_a_idx
+
+DROP INDEX tmp2_a_idx;
+CREATE INDEX tmp2_a_idx ON ONLY tmp2 (a);
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+\d tmp2
+\d tmp2_a_idx
+
+CREATE INDEX tmp2_p1_a_idx ON tmp2_p1 (a);
+ALTER INDEX tmp2_a_idx ATTACH PARTITION tmp2_p1_a_idx;
+SELECT c.relname, i.indisvalid AS global_valid, t.indisvalid AS local_valid
+ FROM pg_class c JOIN pg_index i ON i.indexrelid = c.oid
+ LEFT JOIN pg_temp_index t ON t.indexrelid = i.indexrelid
+ WHERE c.relname ~ 'tmp2(.*)_a_idx'
+ ORDER BY c.relname;
+\d tmp2
+\d tmp2_a_idx
+\d tmp2_p1_a_idx
+DROP TABLE tmp2;
+
-- Test ALTER TABLE with rewrite
CREATE GLOBAL TEMP TABLE tmp2 (a int);
INSERT INTO tmp2 VALUES (1);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 6b38155a48a..e2e00c053dd 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -957,6 +957,7 @@ FormData_pg_subscription
FormData_pg_subscription_rel
FormData_pg_tablespace
FormData_pg_temp_class
+FormData_pg_temp_index
FormData_pg_transform
FormData_pg_trigger
FormData_pg_ts_config
@@ -1023,6 +1024,7 @@ Form_pg_subscription
Form_pg_subscription_rel
Form_pg_tablespace
Form_pg_temp_class
+Form_pg_temp_index
Form_pg_transform
Form_pg_trigger
Form_pg_ts_config
--
2.51.0