From 89185a16c0a9e53a08ad564c0ba8b56b83034ec6 Mon Sep 17 00:00:00 2001 From: Dean Rasheed Date: Tue, 9 Jun 2026 19:00:09 +0100 Subject: [PATCH v7 04/10] Support global temporary sequences. --- src/backend/catalog/global_temp.c | 5 + src/backend/commands/sequence.c | 66 ++++++++ src/backend/parser/parse_utilcmd.c | 3 +- src/bin/pg_dump/pg_dump.c | 4 +- src/bin/psql/describe.c | 6 + src/include/commands/sequence.h | 1 + src/test/isolation/expected/global-temp.out | 176 ++++++++++---------- src/test/isolation/specs/global-temp.spec | 2 +- src/test/regress/expected/global_temp.out | 121 +++++++++----- src/test/regress/sql/global_temp.sql | 10 +- 10 files changed, 257 insertions(+), 137 deletions(-) diff --git a/src/backend/catalog/global_temp.c b/src/backend/catalog/global_temp.c index 3863cb12837..befee0744e2 100644 --- a/src/backend/catalog/global_temp.c +++ b/src/backend/catalog/global_temp.c @@ -62,6 +62,7 @@ #include "access/xlogutils.h" #include "catalog/global_temp.h" #include "catalog/storage.h" +#include "commands/sequence.h" #include "commands/tablecmds.h" #include "lib/dshash.h" #include "miscadmin.h" @@ -873,6 +874,10 @@ InitGlobalTempRelation(Relation relation) if (nblocks > 0) relation->rd_index->indisvalid = false; } + + /* If it's a sequence, initialize it */ + if (relation->rd_rel->relkind == RELKIND_SEQUENCE) + InitGlobalTempSequence(relation); } /* The remaining initialization works as if we had created it locally */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 551667650ba..9a7cace6c37 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -321,6 +321,72 @@ ResetSequence(Oid seq_relid) sequence_close(seq_rel, NoLock); } +/* + * InitGlobalTempSequence - initialize a global temporary sequence + * + * This is called the first time a global temporary sequence is accessed from + * a backend other than the backend that created it. On entry, the sequence + * should have valid catalog entries, and its physical disk file should have + * been created, but be empty. + */ +void +InitGlobalTempSequence(Relation seq_rel) +{ + Oid seq_relid = RelationGetRelid(seq_rel); + SeqTable elm; + HeapTuple pgstuple; + Form_pg_sequence pgsform; + int64 startv; + int i; + Datum value[SEQ_COL_LASTCOL]; + bool null[SEQ_COL_LASTCOL]; + TupleDesc tupDesc; + HeapTuple tuple; + + /* Find or create a hash table entry for this sequence */ + if (seqhashtab == NULL) + create_seq_hashtable(); + + elm = (SeqTable) hash_search(seqhashtab, &seq_relid, HASH_ENTER, NULL); + + /* Initialize the sequence state */ + elm->filenumber = seq_rel->rd_rel->relfilenode; + elm->lxid = InvalidLocalTransactionId; + elm->last_valid = false; + elm->last = elm->cached = 0; + + /* Read the sequence definition from pg_sequence */ + pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid)); + if (!HeapTupleIsValid(pgstuple)) + elog(ERROR, "cache lookup failed for sequence %u", seq_relid); + pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple); + startv = pgsform->seqstart; + ReleaseSysCache(pgstuple); + + /* Build a new sequence tuple */ + for (i = SEQ_COL_FIRSTCOL; i <= SEQ_COL_LASTCOL; i++) + { + switch (i) + { + case SEQ_COL_LASTVAL: + value[i - 1] = Int64GetDatumFast(startv); + break; + case SEQ_COL_LOG: + value[i - 1] = Int64GetDatum((int64) 0); + break; + case SEQ_COL_CALLED: + value[i - 1] = BoolGetDatum(false); + break; + } + null[i - 1] = false; + } + tupDesc = RelationGetDescr(seq_rel); + tuple = heap_form_tuple(tupDesc, value, null); + + /* Initialize the sequence's data */ + fill_seq_with_data(seq_rel, tuple); +} + /* * Initialize a sequence's relation with the specified tuple as content * diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 58ccc7b68f2..2b36d6c7f98 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -496,7 +496,8 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqpersistence = cxt->rel ? cxt->rel->rd_rel->relpersistence : cxt->relation->relpersistence; if (loggedEl) { - if (seqpersistence == RELPERSISTENCE_TEMP) + if (seqpersistence == RELPERSISTENCE_TEMP || + seqpersistence == RELPERSISTENCE_GLOBAL_TEMP) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot set logged status of a temporary sequence"), diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 433c7de86ab..baada2f7cc4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -19317,7 +19317,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) appendPQExpBuffer(query, "CREATE %sSEQUENCE %s\n", tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? - "UNLOGGED " : "", + "UNLOGGED " : + tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? + "GLOBAL TEMP " : "", fmtQualifiedDumpable(tbinfo)); if (seq->seqtype != SEQTYPE_BIGINT) diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index daaa9f9e69d..8ce4decb91e 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -1806,6 +1806,12 @@ describeOneTableDetails(const char *schemaname, if (tableinfo.relpersistence == RELPERSISTENCE_UNLOGGED) printfPQExpBuffer(&title, _("Unlogged sequence \"%s.%s\""), schemaname, relationname); + else if (tableinfo.relpersistence == RELPERSISTENCE_TEMP) + printfPQExpBuffer(&title, _("Temporary sequence \"%s.%s\""), + schemaname, relationname); + else if (tableinfo.relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + printfPQExpBuffer(&title, _("Global temporary sequence \"%s.%s\""), + schemaname, relationname); else printfPQExpBuffer(&title, _("Sequence \"%s.%s\""), schemaname, relationname); diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index 2c3c4a3f074..6f514ac6dd1 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -47,6 +47,7 @@ extern ObjectAddress AlterSequence(ParseState *pstate, AlterSeqStmt *stmt); extern void SequenceChangePersistence(Oid relid, char newrelpersistence); extern void DeleteSequenceTuple(Oid relid); extern void ResetSequence(Oid seq_relid); +extern void InitGlobalTempSequence(Relation seq_rel); extern void SetSequence(Oid relid, int64 next, bool iscalled); extern void ResetSequenceCaches(void); diff --git a/src/test/isolation/expected/global-temp.out b/src/test/isolation/expected/global-temp.out index 3a6af7eb23e..d46ca85acbb 100644 --- a/src/test/isolation/expected/global-temp.out +++ b/src/test/isolation/expected/global-temp.out @@ -4,15 +4,15 @@ starting permutation: ins1 ins2 sel1 sel2 step ins1: INSERT INTO tmp VALUES (1, 's1'); step ins2: INSERT INTO tmp VALUES (1, 's2'); step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) @@ -41,28 +41,28 @@ step ins1: INSERT INTO tmp VALUES (1, 's1'); step b2: BEGIN; step ins2: INSERT INTO tmp VALUES (1, 's2'); step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step c2: COMMIT; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) @@ -71,27 +71,27 @@ step ins1: INSERT INTO tmp VALUES (1, 's1'); step b2: BEGIN; step ins2: INSERT INTO tmp VALUES (1, 's2'); step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step r2: ROLLBACK; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- +key|val|seq +---+---+--- (0 rows) @@ -100,28 +100,28 @@ step ins1: INSERT INTO tmp VALUES (1, 's1'); step b2: BEGIN; step ins2: INSERT INTO tmp VALUES (1, 's2'); step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step sp2: SAVEPOINT sp; step r2: ROLLBACK; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- +key|val|seq +---+---+--- (0 rows) @@ -131,39 +131,39 @@ step b2: BEGIN; step sp2: SAVEPOINT sp; step ins2: INSERT INTO tmp VALUES (1, 's2'); step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step rsp2: ROLLBACK TO SAVEPOINT sp; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- +key|val|seq +---+---+--- (0 rows) step r2: ROLLBACK; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- +key|val|seq +---+---+--- (0 rows) @@ -175,27 +175,27 @@ step sp2: SAVEPOINT sp; step t2: TRUNCATE tmp; step rsp2: ROLLBACK TO SAVEPOINT sp; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step r2: ROLLBACK; step sel1: SELECT * FROM tmp; -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2: SELECT * FROM tmp; -key|val ----+--- +key|val|seq +---+---+--- (0 rows) @@ -267,9 +267,9 @@ Index Scan using tmp_val_idx on tmp Index Cond: (val = 's1'::text) (2 rows) -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step ins2: INSERT INTO tmp VALUES (1, 's2'); @@ -286,9 +286,9 @@ Index Scan using tmp_val_idx on tmp Index Cond: (val = 's2'::text) (2 rows) -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) @@ -309,9 +309,9 @@ Index Scan using tmp_val_idx on tmp Index Cond: (val = 's1'::text) (2 rows) -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2_idx: @@ -328,9 +328,9 @@ Seq Scan on tmp Filter: (val = 's2'::text) (3 rows) -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) @@ -351,9 +351,9 @@ Index Scan using tmp_val_idx on tmp Index Cond: (val = 's1'::text) (2 rows) -key|val ----+--- - 1|s1 +key|val|seq +---+---+--- + 1|s1 | 1 (1 row) step sel2_idx: @@ -370,9 +370,9 @@ Seq Scan on tmp Filter: (val = 's2'::text) (3 rows) -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) step reidx2: REINDEX INDEX tmp_val_idx; @@ -389,8 +389,8 @@ Index Scan using tmp_val_idx on tmp Index Cond: (val = 's2'::text) (2 rows) -key|val ----+--- - 1|s2 +key|val|seq +---+---+--- + 1|s2 | 1 (1 row) diff --git a/src/test/isolation/specs/global-temp.spec b/src/test/isolation/specs/global-temp.spec index 5136230e859..4ec963e104a 100644 --- a/src/test/isolation/specs/global-temp.spec +++ b/src/test/isolation/specs/global-temp.spec @@ -1,7 +1,7 @@ # Test global temporary relations setup { - CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text); + CREATE GLOBAL TEMP TABLE tmp (key int PRIMARY KEY, val text, seq serial); 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); diff --git a/src/test/regress/expected/global_temp.out b/src/test/regress/expected/global_temp.out index 1b2450a49b5..6d2751e6899 100644 --- a/src/test/regress/expected/global_temp.out +++ b/src/test/regress/expected/global_temp.out @@ -13,16 +13,17 @@ 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 PRIMARY KEY, b text); +CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text, c serial); 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); \d tmp1 - Global temporary table "global_temp_tests.tmp1" - Column | Type | Collation | Nullable | Default ---------+---------+-----------+----------+--------- + Global temporary table "global_temp_tests.tmp1" + Column | Type | Collation | Nullable | Default +--------+---------+-----------+----------+--------------------------------- a | integer | | not null | b | text | | | + c | integer | | not null | nextval('tmp1_c_seq'::regclass) Indexes: "tmp1_pkey" PRIMARY KEY, btree (a) @@ -54,16 +55,16 @@ NOTICE: drop cascades to table global_temp_yyy.tmp3 -- Basic tests INSERT INTO tmp1 VALUES (1, 'xxx'); SELECT * FROM tmp1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) \c SET search_path = global_temp_tests; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) -- Test index @@ -78,9 +79,9 @@ SELECT * FROM tmp1 WHERE a = 1; (2 rows) SELECT * FROM tmp1 WHERE a = 1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) RESET enable_seqscan; @@ -96,9 +97,9 @@ SELECT * FROM tmp1 WHERE b = 'xxx'; (2 rows) SELECT * FROM tmp1 WHERE b = 'xxx'; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) RESET enable_seqscan; @@ -145,8 +146,8 @@ 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 | b ----+--- + a | b | c +---+---+--- (0 rows) PREPARE TRANSACTION 'twophase'; -- fail @@ -272,83 +273,113 @@ INSERT INTO tmp1 VALUES (1, 'xxx'); BEGIN; TRUNCATE tmp1; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) ROLLBACK; SELECT * FROM tmp1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) BEGIN; SAVEPOINT sp1; TRUNCATE tmp1; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) RELEASE sp1; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) ROLLBACK; SELECT * FROM tmp1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) BEGIN; SAVEPOINT sp1; TRUNCATE tmp1; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) ROLLBACK TO sp1; SELECT * FROM tmp1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) COMMIT; SELECT * FROM tmp1; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 1 (1 row) TRUNCATE tmp1; SELECT * FROM tmp1; - a | b ----+--- + a | b | c +---+---+--- (0 rows) -- Test view creation INSERT INTO tmp1 VALUES (1, 'xxx'); CREATE VIEW v AS SELECT * FROM tmp1; SELECT * FROM v; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 2 (1 row) DROP VIEW v; CREATE TEMP VIEW v AS SELECT * FROM tmp1; SELECT * FROM v; - a | b ----+----- - 1 | xxx + a | b | c +---+-----+--- + 1 | xxx | 2 (1 row) DROP VIEW v; CREATE GLOBAL TEMP VIEW v AS SELECT * FROM tmp1; -- fail ERROR: views cannot be global temporary because they do not have storage +-- Test global temp sequence +CREATE GLOBAL TEMP SEQUENCE s MINVALUE 100 MAXVALUE 130 INCREMENT 10 START WITH 110 CYCLE; +\d s + Global temporary sequence "global_temp_tests.s" + Type | Start | Minimum | Maximum | Increment | Cycles? | Cache +--------+-------+---------+---------+-----------+---------+------- + bigint | 110 | 100 | 130 | 10 | yes | 1 + +SELECT nextval('s') FROM generate_series(1, 5); + nextval +--------- + 110 + 120 + 130 + 100 + 110 +(5 rows) + +\c +SET search_path = global_temp_tests; +SELECT nextval('s') FROM generate_series(1, 5); + nextval +--------- + 110 + 120 + 130 + 100 + 110 +(5 rows) + diff --git a/src/test/regress/sql/global_temp.sql b/src/test/regress/sql/global_temp.sql index 7255a35f2d4..2373eae0ad1 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 PRIMARY KEY, b text); +CREATE GLOBAL TEMP TABLE tmp1 (a int PRIMARY KEY, b text, c serial); 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); @@ -189,3 +189,11 @@ SELECT * FROM v; DROP VIEW v; CREATE GLOBAL TEMP VIEW v AS SELECT * FROM tmp1; -- fail + +-- Test global temp sequence +CREATE GLOBAL TEMP SEQUENCE s MINVALUE 100 MAXVALUE 130 INCREMENT 10 START WITH 110 CYCLE; +\d s +SELECT nextval('s') FROM generate_series(1, 5); +\c +SET search_path = global_temp_tests; +SELECT nextval('s') FROM generate_series(1, 5); -- 2.51.0