From c5cc76dfce0c8858f536e3620cc3fd60d34df565 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Tue, 9 Jun 2026 13:50:47 +0100 Subject: [PATCH v1 2/9] Support indexes on global temporary tables. As with local temporary tables, concurrent index build/reindex/drop is not supported. --- contrib/bloom/blinsert.c | 6 +- contrib/bloom/bloom.h | 2 +- src/backend/access/brin/brin.c | 4 +- src/backend/access/gin/gininsert.c | 8 +- src/backend/access/gist/gist.c | 6 +- src/backend/access/hash/hash.c | 12 +- src/backend/access/nbtree/nbtree.c | 6 +- src/backend/access/spgist/spginsert.c | 6 +- src/backend/access/transam/xloginsert.c | 3 +- src/backend/catalog/global_temp.c | 24 +++ src/backend/catalog/index.c | 5 +- src/backend/commands/indexcmds.c | 27 ++- src/backend/optimizer/plan/planner.c | 1 + src/bin/scripts/reindexdb.c | 4 + src/include/access/amapi.h | 4 +- src/include/access/brin_internal.h | 2 +- src/include/access/gin_private.h | 2 +- src/include/access/gist_private.h | 2 +- src/include/access/hash.h | 2 +- src/include/access/nbtree.h | 2 +- src/include/access/spgist.h | 2 +- src/test/isolation/expected/global-temp.out | 144 ++++++++++++++++ src/test/isolation/specs/global-temp.spec | 25 ++- .../modules/dummy_index_am/dummy_index_am.c | 4 +- src/test/regress/expected/global_temp.out | 161 ++++++++++++------ src/test/regress/sql/global_temp.sql | 41 ++++- 26 files changed, 407 insertions(+), 98 deletions(-) diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c index df24856d9ae..804c77ae768 100644 --- a/contrib/bloom/blinsert.c +++ b/contrib/bloom/blinsert.c @@ -159,13 +159,13 @@ blbuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * Build an empty bloom index in the initialization fork. + * Build an empty bloom index in the specified fork. */ void -blbuildempty(Relation index) +blbuildempty(Relation index, ForkNumber forknum) { /* Initialize the meta page */ - BloomInitMetapage(index, INIT_FORKNUM); + BloomInitMetapage(index, forknum); } /* diff --git a/contrib/bloom/bloom.h b/contrib/bloom/bloom.h index 9a4125bdfb1..4f00584006b 100644 --- a/contrib/bloom/bloom.h +++ b/contrib/bloom/bloom.h @@ -197,7 +197,7 @@ extern void blrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys, extern void blendscan(IndexScanDesc scan); extern IndexBuildResult *blbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void blbuildempty(Relation index); +extern void blbuildempty(Relation index, ForkNumber forknum); extern IndexBulkDeleteResult *blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state); diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c index bdb30752e09..94494c9d8fa 100644 --- a/src/backend/access/brin/brin.c +++ b/src/backend/access/brin/brin.c @@ -1276,12 +1276,12 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo) } void -brinbuildempty(Relation index) +brinbuildempty(Relation index, ForkNumber forknum) { Buffer metabuf; /* An empty BRIN index has a metapage only. */ - metabuf = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL, + metabuf = ExtendBufferedRel(BMR_REL(index), forknum, NULL, EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); /* Initialize and xlog metabuffer. */ diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index cb9ed3b563c..e9c2d1eedf1 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -806,18 +806,18 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * ginbuildempty() -- build an empty gin index in the initialization fork + * ginbuildempty() -- build an empty gin index in the specified fork */ void -ginbuildempty(Relation index) +ginbuildempty(Relation index, ForkNumber forknum) { Buffer RootBuffer, MetaBuffer; /* An empty GIN index has two pages. */ - MetaBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL, + MetaBuffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL, EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); - RootBuffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL, + RootBuffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL, EB_LOCK_FIRST | EB_SKIP_EXTENSION_LOCK); /* Initialize and xlog metabuffer and root buffer. */ diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c index 8565e225be7..9eae449786b 100644 --- a/src/backend/access/gist/gist.c +++ b/src/backend/access/gist/gist.c @@ -134,15 +134,15 @@ createTempGistContext(void) } /* - * gistbuildempty() -- build an empty gist index in the initialization fork + * gistbuildempty() -- build an empty gist index in the specified fork */ void -gistbuildempty(Relation index) +gistbuildempty(Relation index, ForkNumber forknum) { Buffer buffer; /* Initialize the root page */ - buffer = ExtendBufferedRel(BMR_REL(index), INIT_FORKNUM, NULL, + buffer = ExtendBufferedRel(BMR_REL(index), forknum, NULL, EB_SKIP_EXTENSION_LOCK | EB_LOCK_FIRST); /* Initialize and xlog buffer */ diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 8d8cd30dc38..cdcbb9efae0 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -175,10 +175,10 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) * metapage, nor the first bitmap page. */ sort_threshold = (maintenance_work_mem * (Size) 1024) / BLCKSZ; - if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP) - sort_threshold = Min(sort_threshold, NBuffers); - else + if (RelationUsesLocalBuffers(index)) sort_threshold = Min(sort_threshold, NLocBuffer); + else + sort_threshold = Min(sort_threshold, NBuffers); if (num_buckets >= sort_threshold) buildstate.spool = _h_spoolinit(heap, index, num_buckets); @@ -215,12 +215,12 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * hashbuildempty() -- build an empty hash index in the initialization fork + * hashbuildempty() -- build an empty hash index in the specified fork */ void -hashbuildempty(Relation index) +hashbuildempty(Relation index, ForkNumber forknum) { - _hash_init(index, 0, INIT_FORKNUM); + _hash_init(index, 0, forknum); } /* diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index 3df2c752ead..be1b5ce51f9 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -177,16 +177,16 @@ bthandler(PG_FUNCTION_ARGS) } /* - * btbuildempty() -- build an empty btree index in the initialization fork + * btbuildempty() -- build an empty btree index in the specified fork */ void -btbuildempty(Relation index) +btbuildempty(Relation index, ForkNumber forknum) { bool allequalimage = _bt_allequalimage(index, false); BulkWriteState *bulkstate; BulkWriteBuffer metabuf; - bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM); + bulkstate = smgr_bulk_start_rel(index, forknum); /* Construct metapage. */ metabuf = smgr_bulk_get_buf(bulkstate); diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c index 780ef646a54..1a14f84174a 100644 --- a/src/backend/access/spgist/spginsert.c +++ b/src/backend/access/spgist/spginsert.c @@ -148,15 +148,15 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * Build an empty SPGiST index in the initialization fork + * Build an empty SPGiST index in the specified fork */ void -spgbuildempty(Relation index) +spgbuildempty(Relation index, ForkNumber forknum) { BulkWriteState *bulkstate; BulkWriteBuffer buf; - bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM); + bulkstate = smgr_bulk_start_rel(index, forknum); /* Construct metapage. */ buf = smgr_bulk_get_buf(bulkstate); diff --git a/src/backend/access/transam/xloginsert.c b/src/backend/access/transam/xloginsert.c index f2e10b82b7d..9b099d87f35 100644 --- a/src/backend/access/transam/xloginsert.c +++ b/src/backend/access/transam/xloginsert.c @@ -561,7 +561,8 @@ XLogSimpleInsertInt64(RmgrId rmid, uint8 info, int64 value) XLogRecPtr XLogGetFakeLSN(Relation rel) { - if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { /* * Temporary relations are only accessible in our session, so a simple diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c index a3ee3d424ca..8c105f71744 100644 --- a/src/backend/catalog/global_temp.c +++ b/src/backend/catalog/global_temp.c @@ -53,7 +53,9 @@ */ #include "postgres.h" +#include "access/amapi.h" #include "access/genam.h" +#include "access/table.h" #include "access/tableam.h" #include "access/xact.h" #include "access/xlogutils.h" @@ -832,6 +834,28 @@ InitGlobalTempRelation(Relation relation) relation->rd_locator, relation->rd_rel->relpersistence, true); + + /* + * If it's an index, build an empty index in the main fork. + * + * If the table is not empty (can happen if another session added the + * index after we populated the table), then mark it as invalid. The + * user will need to do a REINDEX to build it. + */ + if (relation->rd_rel->relkind == RELKIND_INDEX) + { + Relation heapRelation; + BlockNumber nblocks; + + relation->rd_indam->ambuildempty(relation, MAIN_FORKNUM); + + heapRelation = table_open(relation->rd_index->indrelid, AccessShareLock); + nblocks = RelationGetNumberOfBlocks(heapRelation); + table_close(heapRelation, AccessShareLock); + + if (nblocks > 0) + relation->rd_index->indisvalid = false; + } } /* The remaining initialization works as if we had created it locally */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 9407c357f27..9d37a773b46 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2156,7 +2156,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) * lock (see comments in RemoveRelations), and a non-concurrent DROP is * more efficient. */ - Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP || + Assert((get_rel_persistence(indexId) != RELPERSISTENCE_TEMP && + get_rel_persistence(indexId) != RELPERSISTENCE_GLOBAL_TEMP) || (!concurrent && !concurrent_lock_mode)); /* @@ -3112,7 +3113,7 @@ index_build(Relation heapRelation, { smgrcreate(RelationGetSmgr(indexRelation), INIT_FORKNUM, false); log_smgrcreate(&indexRelation->rd_locator, INIT_FORKNUM); - indexRelation->rd_indam->ambuildempty(indexRelation); + indexRelation->rd_indam->ambuildempty(indexRelation, INIT_FORKNUM); } /* diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 9ab74c8df0a..25d828f3a2b 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -616,8 +616,16 @@ DefineIndex(ParseState *pstate, * is more efficient. Do this before any use of the concurrent option is * done. */ - if (stmt->concurrent && get_rel_persistence(tableId) != RELPERSISTENCE_TEMP) - concurrent = true; + if (stmt->concurrent) + { + char relpersistence = get_rel_persistence(tableId); + + if (relpersistence == RELPERSISTENCE_TEMP || + relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + concurrent = false; + else + concurrent = true; + } else concurrent = false; @@ -2981,7 +2989,8 @@ ReindexIndex(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLev if (relkind == RELKIND_PARTITIONED_INDEX) ReindexPartitions(stmt, indOid, params, isTopLevel); else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - persistence != RELPERSISTENCE_TEMP) + persistence != RELPERSISTENCE_TEMP && + persistence != RELPERSISTENCE_GLOBAL_TEMP) ReindexRelationConcurrently(stmt, indOid, params); else { @@ -3077,6 +3086,7 @@ static Oid ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLevel) { Oid heapOid; + char persistence; bool result; const RangeVar *relation = stmt->relation; @@ -3094,10 +3104,13 @@ ReindexTable(const ReindexStmt *stmt, const ReindexParams *params, bool isTopLev 0, RangeVarCallbackMaintainsTable, NULL); + persistence = get_rel_persistence(heapOid); + if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE) ReindexPartitions(stmt, heapOid, params, isTopLevel); else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP) + persistence != RELPERSISTENCE_TEMP && + persistence != RELPERSISTENCE_GLOBAL_TEMP) { result = ReindexRelationConcurrently(stmt, heapOid, params); @@ -3521,7 +3534,8 @@ ReindexMultipleInternal(const ReindexStmt *stmt, const List *relids, const Reind Assert(!RELKIND_HAS_PARTITIONS(relkind)); if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - relpersistence != RELPERSISTENCE_TEMP) + relpersistence != RELPERSISTENCE_TEMP && + relpersistence != RELPERSISTENCE_GLOBAL_TEMP) { ReindexParams newparams = *params; @@ -3963,7 +3977,8 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein idx->amId = indexRel->rd_rel->relam; /* This function shouldn't be called for temporary relations. */ - if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + if (indexRel->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + indexRel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) elog(ERROR, "cannot reindex a temporary table concurrently"); pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX, idx->tableId); diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index f4689e7c9f8..12c616dc6cd 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -7352,6 +7352,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * safe. */ if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) { diff --git a/src/bin/scripts/reindexdb.c b/src/bin/scripts/reindexdb.c index d7fb16d3c85..70da07d2369 100644 --- a/src/bin/scripts/reindexdb.c +++ b/src/bin/scripts/reindexdb.c @@ -655,6 +655,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type, CppAsString2(RELKIND_MATVIEW) ")\n" " AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP) "\n" + " AND c.relpersistence != " + CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) "\n" " ORDER BY c.relpages DESC;"); break; @@ -678,6 +680,8 @@ get_parallel_tables_list(PGconn *conn, ReindexType type, CppAsString2(RELKIND_MATVIEW) ")\n" " AND c.relpersistence != " CppAsString2(RELPERSISTENCE_TEMP) "\n" + " AND c.relpersistence != " + CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) "\n" " AND ns.nspname IN ("); for (cell = user_list->head; cell; cell = cell->next) diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h index 79240333530..b3b9cb6039b 100644 --- a/src/include/access/amapi.h +++ b/src/include/access/amapi.h @@ -15,6 +15,7 @@ #include "access/cmptype.h" #include "access/genam.h" #include "access/stratnum.h" +#include "common/relpath.h" #include "nodes/nodes.h" #include "nodes/pg_list.h" @@ -115,7 +116,8 @@ typedef IndexBuildResult *(*ambuild_function) (Relation heapRelation, IndexInfo *indexInfo); /* build empty index */ -typedef void (*ambuildempty_function) (Relation indexRelation); +typedef void (*ambuildempty_function) (Relation indexRelation, + ForkNumber forknum); /* insert this tuple */ typedef bool (*aminsert_function) (Relation indexRelation, diff --git a/src/include/access/brin_internal.h b/src/include/access/brin_internal.h index e17c7fee511..10137275c4c 100644 --- a/src/include/access/brin_internal.h +++ b/src/include/access/brin_internal.h @@ -90,7 +90,7 @@ extern BrinDesc *brin_build_desc(Relation rel); extern void brin_free_desc(BrinDesc *bdesc); extern IndexBuildResult *brinbuild(Relation heap, Relation index, IndexInfo *indexInfo); -extern void brinbuildempty(Relation index); +extern void brinbuildempty(Relation index, ForkNumber forknum); extern bool brininsert(Relation idxRel, Datum *values, bool *nulls, ItemPointer heaptid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/include/access/gin_private.h b/src/include/access/gin_private.h index 6725ee2839f..1ce7bffbe70 100644 --- a/src/include/access/gin_private.h +++ b/src/include/access/gin_private.h @@ -110,7 +110,7 @@ extern char *ginbuildphasename(int64 phasenum); /* gininsert.c */ extern IndexBuildResult *ginbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void ginbuildempty(Relation index); +extern void ginbuildempty(Relation index, ForkNumber forknum); extern bool gininsert(Relation index, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h index 44514f1cb8d..334c3de4541 100644 --- a/src/include/access/gist_private.h +++ b/src/include/access/gist_private.h @@ -399,7 +399,7 @@ typedef struct GiSTOptions } GiSTOptions; /* gist.c */ -extern void gistbuildempty(Relation index); +extern void gistbuildempty(Relation index, ForkNumber forknum); extern bool gistinsert(Relation r, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/include/access/hash.h b/src/include/access/hash.h index a8702f0e5ea..a719d374af1 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -362,7 +362,7 @@ typedef struct HashOptions extern IndexBuildResult *hashbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void hashbuildempty(Relation index); +extern void hashbuildempty(Relation index, ForkNumber forknum); extern bool hashinsert(Relation rel, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h index 3097e9bb1af..8053dfe3d6a 100644 --- a/src/include/access/nbtree.h +++ b/src/include/access/nbtree.h @@ -1151,7 +1151,7 @@ typedef struct BTOptions /* * external entry points for btree, in nbtree.c */ -extern void btbuildempty(Relation index); +extern void btbuildempty(Relation index, ForkNumber forknum); extern bool btinsert(Relation rel, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h index 083d93f8ffd..392c6538cbf 100644 --- a/src/include/access/spgist.h +++ b/src/include/access/spgist.h @@ -195,7 +195,7 @@ extern bytea *spgoptions(Datum reloptions, bool validate); /* spginsert.c */ extern IndexBuildResult *spgbuild(Relation heap, Relation index, struct IndexInfo *indexInfo); -extern void spgbuildempty(Relation index); +extern void spgbuildempty(Relation index, ForkNumber forknum); extern bool spginsert(Relation index, Datum *values, bool *isnull, ItemPointer ht_ctid, Relation heapRel, IndexUniqueCheck checkUnique, diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out index 7951c3c4910..78d944d34f8 100644 --- a/src/test/isolation/expected/global-temp.out +++ b/src/test/isolation/expected/global-temp.out @@ -240,3 +240,147 @@ key|pg_typeof|val (1 row) step drop1: DROP TABLE tmp2; + +starting permutation: ins1 idx1 sel1_idx ins2 sel2_idx +step ins1: INSERT INTO tmp VALUES (1, 's1'); +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 +---+--- + 1|s1 +(1 row) + +step ins2: INSERT INTO tmp VALUES (1, 's2'); +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 +---+--- + 1|s2 +(1 row) + + +starting permutation: ins1 ins2 idx1 sel1_idx 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 +---+--- + 1|s1 +(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 +---+--- + 1|s2 +(1 row) + + +starting permutation: ins1 ins2 idx1 sel1_idx 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 +---+--- + 1|s1 +(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 +---+--- + 1|s2 +(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 +---+--- + 1|s2 +(1 row) + diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec index 32e6e93df07..f33170260ce 100644 --- a/src/test/isolation/specs/global-temp.spec +++ b/src/test/isolation/specs/global-temp.spec @@ -1,9 +1,9 @@ # Test global temporary relations setup { - CREATE GLOBAL TEMP TABLE tmp (key int, val text); + CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text); - CREATE GLOBAL TEMP TABLE tmp_parted (key int, val text) PARTITION BY LIST (key); + CREATE GLOBAL TEMP TABLE tmp_parted (key int PRIMARY KEY, val text) PARTITION BY LIST (key); CREATE GLOBAL TEMP TABLE tmp_p1 PARTITION OF tmp_parted FOR VALUES IN (1); CREATE GLOBAL TEMP TABLE tmp_p2 PARTITION OF tmp_parted FOR VALUES IN ((2), (3)); } @@ -24,6 +24,14 @@ step alter1a { ALTER TABLE tmp2 ALTER COLUMN key SET DATA TYPE numeric; } step alter1b { ALTER TABLE tmp2 ALTER COLUMN val SET NOT NULL; } step seltype1 { SELECT key, pg_typeof(key), val FROM tmp2; } step drop1 { DROP TABLE tmp2; } +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'; +} session s2 step b2 { BEGIN; } @@ -39,6 +47,14 @@ step sp2 { SAVEPOINT sp; } step rsp2 { ROLLBACK TO SAVEPOINT sp; } step ins2_2 { INSERT INTO tmp2 VALUES (1, 's2'); } step seltype2 { SELECT key, pg_typeof(key), val FROM tmp2; } +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'; +} +step reidx2 { REINDEX INDEX tmp_val_idx; } # Basic effects permutation ins1 ins2 sel1 sel2 @@ -54,3 +70,8 @@ permutation ins1 b2 ins2 sp2 t2 rsp2 sel1 sel2 r2 sel1 sel2 # Test prevention of ALTER TABLE with rewrite, if in use permutation create1 ins1_2 alter1a alter1b ins2_2 seltype1 seltype2 drop1 permutation create1 ins1_2 ins2_2 alter1a alter1b seltype1 seltype2 drop1 + +# Test val index +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 diff --git a/src/test/modules/dummy_index_am/dummy_index_am.c b/src/test/modules/dummy_index_am/dummy_index_am.c index 31f8d2b8161..65b116280bf 100644 --- a/src/test/modules/dummy_index_am/dummy_index_am.c +++ b/src/test/modules/dummy_index_am/dummy_index_am.c @@ -166,10 +166,10 @@ dibuild(Relation heap, Relation index, IndexInfo *indexInfo) } /* - * Build an empty index for the initialization fork. + * Build an empty index for the specified fork. */ static void -dibuildempty(Relation index) +dibuildempty(Relation index, ForkNumber forknum) { /* No need to build an init fork for a dummy index */ } diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out index f34bd83b0cb..ee10cb7b5d0 100644 --- a/src/test/regress/expected/global_temp.out +++ b/src/test/regress/expected/global_temp.out @@ -13,7 +13,7 @@ CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int); -- fail ERROR: cannot create global temporary relation in temporary schema LINE 1: CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int); ^ -CREATE GLOBAL TEMP TABLE tmp1 (a int); +CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text); CREATE SCHEMA global_temp_xxx CREATE GLOBAL TEMP TABLE tmp2 (a int); CREATE SCHEMA global_temp_yyy; CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int); @@ -21,15 +21,18 @@ CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int); Global temporary table "global_temp_tests.tmp1" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- - a | integer | | | + a | integer | | not null | + b | text | | | +Indexes: + "tmp1_pkey" PRIMARY KEY, btree (a) \dt+ global_temp_*.tmp* - List of tables - Schema | Name | Type | Owner | Persistence | Size | Description --------------------+------+-------+--------------------------+------------------+---------+------------- - global_temp_tests | tmp1 | table | regress_global_temp_user | global temporary | 0 bytes | - global_temp_xxx | tmp2 | table | regress_global_temp_user | global temporary | 0 bytes | - global_temp_yyy | tmp3 | table | regress_global_temp_user | global temporary | 0 bytes | + List of tables + Schema | Name | Type | Owner | Persistence | Size | Description +-------------------+------+-------+--------------------------+------------------+------------+------------- + global_temp_tests | tmp1 | table | regress_global_temp_user | global temporary | 8192 bytes | + global_temp_xxx | tmp2 | table | regress_global_temp_user | global temporary | 0 bytes | + global_temp_yyy | tmp3 | table | regress_global_temp_user | global temporary | 0 bytes | (3 rows) -- Information schema @@ -49,20 +52,59 @@ NOTICE: drop cascades to table global_temp_xxx.tmp2 DROP SCHEMA global_temp_yyy CASCADE; NOTICE: drop cascades to table global_temp_yyy.tmp3 -- Basic tests -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); SELECT * FROM tmp1; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) \c SET search_path = global_temp_tests; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) +-- Test index +INSERT INTO tmp1 VALUES (1, 'xxx'); +SET enable_seqscan = off; +EXPLAIN (COSTS OFF) +SELECT * FROM tmp1 WHERE a = 1; + QUERY PLAN +------------------------------------ + Index Scan using tmp1_pkey on tmp1 + Index Cond: (a = 1) +(2 rows) + +SELECT * FROM tmp1 WHERE a = 1; + a | b +---+----- + 1 | xxx +(1 row) + +RESET enable_seqscan; +-- Test concurrent index build -- CONCURRENTLY is ignored with temp tables +CREATE INDEX CONCURRENTLY tmp1_b_idx ON tmp1(b); +SET enable_seqscan = off; +EXPLAIN (COSTS OFF) +SELECT * FROM tmp1 WHERE b = 'xxx'; + QUERY PLAN +------------------------------------- + Index Scan using tmp1_b_idx on tmp1 + Index Cond: (b = 'xxx'::text) +(2 rows) + +SELECT * FROM tmp1 WHERE b = 'xxx'; + a | b +---+----- + 1 | xxx +(1 row) + +RESET enable_seqscan; +REINDEX INDEX CONCURRENTLY tmp1_b_idx; +REINDEX TABLE CONCURRENTLY tmp1; +DROP INDEX CONCURRENTLY tmp1_b_idx; -- Test ON COMMIT DELETE ROWS CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS; BEGIN; @@ -86,9 +128,10 @@ ERROR: ON COMMIT DROP cannot be used on global temporary tables -- Two-phase commit not allowed with global temp tables BEGIN; SELECT * FROM tmp1; - a ---- -(0 rows) + a | b +---+----- + 1 | xxx +(1 row) PREPARE TRANSACTION 'twophase'; -- fail ERROR: cannot PREPARE a transaction that has operated on temporary objects @@ -130,11 +173,33 @@ DROP TABLE tmp2; -- Test foreign keys CREATE TABLE perm_pk_rel (a int PRIMARY KEY); CREATE TEMP TABLE temp_pk_rel (a int PRIMARY KEY); +CREATE GLOBAL TEMP TABLE gtemp_pk_rel (a int PRIMARY KEY); +CREATE TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail +ERROR: constraints on permanent tables may reference only permanent tables +CREATE TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail +ERROR: constraints on local temporary tables may reference only local temporary tables CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES perm_pk_rel); -- fail ERROR: constraints on global temporary tables may reference only global temporary tables CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES temp_pk_rel); -- fail ERROR: constraints on global temporary tables may reference only global temporary tables -DROP TABLE perm_pk_rel, temp_pk_rel; +CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); +INSERT INTO gtemp_pk_rel VALUES (1); +INSERT INTO tmp2 VALUES (1); +INSERT INTO tmp2 VALUES (2); -- fail +ERROR: insert or update on table "tmp2" violates foreign key constraint "tmp2_a_fkey" +DETAIL: Key (a)=(2) is not present in table "gtemp_pk_rel". +DELETE FROM gtemp_pk_rel WHERE a = 1; -- fail +ERROR: update or delete on table "gtemp_pk_rel" violates foreign key constraint "tmp2_a_fkey" on table "tmp2" +DETAIL: Key (a)=(1) is still referenced from table "tmp2". +ALTER TABLE tmp2 DROP CONSTRAINT tmp2_a_fkey; +ALTER TABLE tmp2 ADD FOREIGN KEY (a) REFERENCES gtemp_pk_rel ON DELETE CASCADE; +DELETE FROM gtemp_pk_rel WHERE a = 1; +SELECT * FROM tmp2; + a +--- +(0 rows) + +DROP TABLE perm_pk_rel, temp_pk_rel, tmp2; -- Test ALTER TABLE ... SET TABLESPACE CREATE GLOBAL TEMP TABLE tmp2 (a int); INSERT INTO tmp2 VALUES (1); @@ -187,85 +252,85 @@ DETAIL: tablespace for table tmp2 DROP TABLE tmp2; DROP TABLESPACE temp_test_tablespace; -- Test TRUNCATE -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); BEGIN; TRUNCATE tmp1; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) ROLLBACK; SELECT * FROM tmp1; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) BEGIN; SAVEPOINT sp1; TRUNCATE tmp1; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) RELEASE sp1; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) ROLLBACK; SELECT * FROM tmp1; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) BEGIN; SAVEPOINT sp1; TRUNCATE tmp1; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) ROLLBACK TO sp1; SELECT * FROM tmp1; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) COMMIT; SELECT * FROM tmp1; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) TRUNCATE tmp1; SELECT * FROM tmp1; - a ---- + a | b +---+--- (0 rows) -- Test view creation -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); CREATE VIEW v AS SELECT * FROM tmp1; SELECT * FROM v; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) DROP VIEW v; CREATE TEMP VIEW v AS SELECT * FROM tmp1; SELECT * FROM v; - a ---- - 1 + a | b +---+----- + 1 | xxx (1 row) DROP VIEW v; diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql index ceb6c0ac7b7..d0f68d130a2 100644 --- a/src/test/regress/sql/global_temp.sql +++ b/src/test/regress/sql/global_temp.sql @@ -11,7 +11,7 @@ SET ROLE regress_global_temp_user; -- Test table creation CREATE GLOBAL TEMP TABLE pg_temp.tmp1 (a int); -- fail -CREATE GLOBAL TEMP TABLE tmp1 (a int); +CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text); CREATE SCHEMA global_temp_xxx CREATE GLOBAL TEMP TABLE tmp2 (a int); CREATE SCHEMA global_temp_yyy; CREATE GLOBAL TEMP TABLE global_temp_yyy.tmp3 (a int); @@ -29,12 +29,31 @@ DROP SCHEMA global_temp_xxx CASCADE; DROP SCHEMA global_temp_yyy CASCADE; -- Basic tests -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); SELECT * FROM tmp1; \c SET search_path = global_temp_tests; SELECT * FROM tmp1; +-- Test index +INSERT INTO tmp1 VALUES (1, 'xxx'); +SET enable_seqscan = off; +EXPLAIN (COSTS OFF) +SELECT * FROM tmp1 WHERE a = 1; +SELECT * FROM tmp1 WHERE a = 1; +RESET enable_seqscan; + +-- Test concurrent index build -- CONCURRENTLY is ignored with temp tables +CREATE INDEX CONCURRENTLY tmp1_b_idx ON tmp1(b); +SET enable_seqscan = off; +EXPLAIN (COSTS OFF) +SELECT * FROM tmp1 WHERE b = 'xxx'; +SELECT * FROM tmp1 WHERE b = 'xxx'; +RESET enable_seqscan; +REINDEX INDEX CONCURRENTLY tmp1_b_idx; +REINDEX TABLE CONCURRENTLY tmp1; +DROP INDEX CONCURRENTLY tmp1_b_idx; + -- Test ON COMMIT DELETE ROWS CREATE GLOBAL TEMP TABLE tmp2 (a int) ON COMMIT DELETE ROWS; BEGIN; @@ -75,9 +94,21 @@ DROP TABLE tmp2; -- Test foreign keys CREATE TABLE perm_pk_rel (a int PRIMARY KEY); CREATE TEMP TABLE temp_pk_rel (a int PRIMARY KEY); +CREATE GLOBAL TEMP TABLE gtemp_pk_rel (a int PRIMARY KEY); +CREATE TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail +CREATE TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); -- fail CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES perm_pk_rel); -- fail CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES temp_pk_rel); -- fail -DROP TABLE perm_pk_rel, temp_pk_rel; +CREATE GLOBAL TEMP TABLE tmp2 (a int REFERENCES gtemp_pk_rel); +INSERT INTO gtemp_pk_rel VALUES (1); +INSERT INTO tmp2 VALUES (1); +INSERT INTO tmp2 VALUES (2); -- fail +DELETE FROM gtemp_pk_rel WHERE a = 1; -- fail +ALTER TABLE tmp2 DROP CONSTRAINT tmp2_a_fkey; +ALTER TABLE tmp2 ADD FOREIGN KEY (a) REFERENCES gtemp_pk_rel ON DELETE CASCADE; +DELETE FROM gtemp_pk_rel WHERE a = 1; +SELECT * FROM tmp2; +DROP TABLE perm_pk_rel, temp_pk_rel, tmp2; -- Test ALTER TABLE ... SET TABLESPACE CREATE GLOBAL TEMP TABLE tmp2 (a int); @@ -110,7 +141,7 @@ DROP TABLE tmp2; DROP TABLESPACE temp_test_tablespace; -- Test TRUNCATE -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); BEGIN; TRUNCATE tmp1; SELECT * FROM tmp1; @@ -139,7 +170,7 @@ TRUNCATE tmp1; SELECT * FROM tmp1; -- Test view creation -INSERT INTO tmp1 VALUES (1); +INSERT INTO tmp1 VALUES (1, 'xxx'); CREATE VIEW v AS SELECT * FROM tmp1; SELECT * FROM v; DROP VIEW v; -- 2.51.0