diff --git a/contrib/amcheck/verify_heapam.c b/contrib/amcheck/verify_heapam.c index f996f9a5727..18f1f7a42c0 100644 --- a/contrib/amcheck/verify_heapam.c +++ b/contrib/amcheck/verify_heapam.c @@ -18,6 +18,7 @@ #include "access/toast_internals.h" #include "access/visibilitymap.h" #include "catalog/pg_am.h" +#include "catalog/storage_gtt.h" #include "funcapi.h" #include "miscadmin.h" #include "storage/bufmgr.h" @@ -340,6 +341,13 @@ verify_heapam(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } + if (RELATION_IS_GLOBAL_TEMP(ctx.rel) && + !gtt_storage_attached(RelationGetRelid(ctx.rel))) + { + relation_close(ctx.rel, AccessShareLock); + PG_RETURN_NULL(); + } + /* Early exit if the relation is empty */ nblocks = RelationGetNumberOfBlocks(ctx.rel); if (!nblocks) diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index d592655258a..051fe802824 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -159,6 +159,18 @@ static relopt_bool boolRelOpts[] = }, true }, + /* + * For global temporary table save on commit clause info to reloptions. + */ + { + { + "on_commit_delete_rows", + "global temporary table on commit options", + RELOPT_KIND_HEAP, + ShareUpdateExclusiveLock + }, + true + }, /* list terminator */ {{NULL}} }; @@ -1834,6 +1846,8 @@ bytea * default_reloptions(Datum reloptions, bool validate, relopt_kind kind) { static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)}, {"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)}, {"autovacuum_enabled", RELOPT_TYPE_BOOL, offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)}, diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index d4bf0c7563d..b623e958c38 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -1024,7 +1024,7 @@ gistproperty(Oid index_oid, int attno, XLogRecPtr gistGetFakeLSN(Relation rel) { - if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + if (RELATION_IS_TEMP(rel)) { /* * Temporary relations are only accessible in our session, so a simple diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index d48c8a45498..a6afb55fbc0 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -152,7 +152,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) * metapage, nor the first bitmap page. */ sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ; - if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP) + if (!RELATION_IS_TEMP(index)) sort_threshold = Min(sort_threshold, NBuffers); else sort_threshold = Min(sort_threshold, NLocBuffer); diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 6ec57f3d8b2..7a143fe545a 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -5835,6 +5835,7 @@ heap_abort_speculative(Relation relation, ItemPointer tid) Buffer buffer; TransactionId prune_xid; + Assert(TransactionIdIsNormal(relation->rd_rel->relfrozenxid)); Assert(ItemPointerIsValid(tid)); block = ItemPointerGetBlockNumber(tid); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 39ef8a0b77d..0bb308d1c5e 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -593,7 +593,7 @@ heapam_relation_set_new_filenode(Relation rel, */ *minmulti = GetOldestMultiXactId(); - srel = RelationCreateStorage(*newrnode, persistence); + srel = RelationCreateStorage(*newrnode, persistence, rel); /* * If required, set up an init fork for an unlogged table so that it can @@ -645,7 +645,7 @@ heapam_relation_copy_data(Relation rel, const RelFileNode *newrnode) * NOTE: any conflict in relfilenode value will be caught in * RelationCreateStorage(). */ - RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence); + RelationCreateStorage(*newrnode, rel->rd_rel->relpersistence, rel); /* copy main fork */ RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index 1749cc2a476..de5914db777 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -324,6 +324,9 @@ heap_vacuum_rel(Relation rel, VacuumParams *params, TransactionId FreezeLimit; MultiXactId MultiXactCutoff; + Assert(TransactionIdIsNormal(rel->rd_rel->relfrozenxid)); + Assert(MultiXactIdIsValid(rel->rd_rel->relminmxid)); + verbose = (params->options & VACOPT_VERBOSE) != 0; instrument = (verbose || (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0)); diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index 6b5f01e1d07..099f942c936 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -28,6 +28,7 @@ #include "access/transam.h" #include "access/xlog.h" #include "access/xloginsert.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "storage/indexfsm.h" #include "storage/lmgr.h" @@ -677,6 +678,14 @@ _bt_getrootheight(Relation rel) { Buffer metabuf; + /* + * If a global temporary table storage file is not initialized in the + * this session, its index does not have a root page, just returns 0. + */ + if (RELATION_IS_GLOBAL_TEMP(rel) && + !gtt_storage_attached(RelationGetRelid(rel))) + return 0; + metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); metad = _bt_getmeta(rel, metabuf); diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index df5268fbc30..8b8a60c2ac5 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -44,6 +44,7 @@ OBJS = \ pg_subscription.o \ pg_type.o \ storage.o \ + storage_gtt.o \ toasting.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index dfd5fb669ee..c0fd68652f7 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -504,6 +504,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) switch (relpersistence) { case RELPERSISTENCE_TEMP: + case RELPERSISTENCE_GLOBAL_TEMP: backend = BackendIdForTempRelations(); break; case RELPERSISTENCE_UNLOGGED: diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 7e99de88b34..019e1105bc4 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -62,6 +62,7 @@ #include "catalog/pg_type.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "executor/executor.h" @@ -102,6 +103,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -349,8 +351,21 @@ heap_create(const char *relname, if (!RELKIND_HAS_TABLESPACE(relkind)) reltablespace = InvalidOid; + /* + * For create global temporary table, initialization storage information + * and recorded in into pg_class, but not initialization stroage file. + * When data is inserted into a temporary table, its storage file is initialized. + */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + create_storage = false; + if (OidIsValid(relfilenode)) + elog(ERROR, "global temporary table can not reuse an existing relfilenode"); + + relfilenode = relid; + } /* Don't create storage for relkinds without physical storage. */ - if (!RELKIND_HAS_STORAGE(relkind)) + else if (!RELKIND_HAS_STORAGE(relkind)) create_storage = false; else { @@ -403,7 +418,7 @@ heap_create(const char *relname, relpersistence, relfrozenxid, relminmxid); else if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind)) - RelationCreateStorage(rel->rd_node, relpersistence); + RelationCreateStorage(rel->rd_node, relpersistence, rel); else Assert(false); } @@ -970,6 +985,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -995,8 +1011,21 @@ AddNewRelationTuple(Relation pg_class_desc, new_rel_reltup->reltuples = 1; } - new_rel_reltup->relfrozenxid = relfrozenxid; - new_rel_reltup->relminmxid = relminmxid; + /* + * The transaction information of the global temporary table is stored + * in hash table, not in pg_class. + */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + new_rel_reltup->relfrozenxid = InvalidTransactionId; + new_rel_reltup->relminmxid = InvalidMultiXactId; + } + else + { + new_rel_reltup->relfrozenxid = relfrozenxid; + new_rel_reltup->relminmxid = relminmxid; + } + new_rel_reltup->relowner = relowner; new_rel_reltup->reltype = new_type_oid; new_rel_reltup->reloftype = reloftype; @@ -1404,6 +1433,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relpersistence, relfrozenxid, relminmxid, PointerGetDatum(relacl), @@ -1486,10 +1516,13 @@ heap_create_with_catalog(const char *relname, StoreConstraints(new_rel_desc, cooked_constraints, is_internal); /* - * If there's a special on-commit action, remember it + * For local temporary table, if there's a special on-commit action, remember it. + * For global temporary table, on-commit action is recorded during initial storage. + * See remember_gtt_storage_info. */ - if (oncommit != ONCOMMIT_NOOP) - register_on_commit_action(relid, oncommit); + if (oncommit != ONCOMMIT_NOOP && + relpersistence != RELPERSISTENCE_GLOBAL_TEMP) + register_on_commit_action(relid, oncommit, false); /* * ok, the relation has been cataloged, so close our relations and return @@ -1986,6 +2019,13 @@ heap_drop_with_catalog(Oid relid) if (relid == defaultPartOid) update_default_partition_oid(parentOid, InvalidOid); + /* + * Only when other sessions are not using this global temporary table, + * is it allowed to drop it. + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + CheckGlobalTempTableNotInUse(rel, "DROP GLOBAL TEMPORARY TABLE"); + /* * Schedule unlinking of the relation's physical files at commit. */ @@ -3272,7 +3312,7 @@ RemoveStatistics(Oid relid, AttrNumber attnum) * the specified relation. Caller must hold exclusive lock on rel. */ static void -RelationTruncateIndexes(Relation heapRelation) +RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode) { ListCell *indlist; @@ -3284,7 +3324,7 @@ RelationTruncateIndexes(Relation heapRelation) IndexInfo *indexInfo; /* Open the index relation; use exclusive lock, just to be sure */ - currentIndex = index_open(indexId, AccessExclusiveLock); + currentIndex = index_open(indexId, lockmode); /* * Fetch info needed for index_build. Since we know there are no @@ -3320,31 +3360,49 @@ RelationTruncateIndexes(Relation heapRelation) * ON COMMIT truncation of temporary tables, where it doesn't matter. */ void -heap_truncate(List *relids) +heap_truncate(List *relids, bool is_global_temp) { List *relations = NIL; - ListCell *cell; + List *lock_modes = NIL; + ListCell *cell_rel; + ListCell *cell_lock; /* Open relations for processing, and grab exclusive access on each */ - foreach(cell, relids) + foreach(cell_rel, relids) { - Oid rid = lfirst_oid(cell); + Oid rid = lfirst_oid(cell_rel); Relation rel; + LOCKMODE lockmode; + + /* + * Truncate global temporary table only clears local data, + * so only low-level locks need to be held. + */ + if (is_global_temp) + { + lockmode = RowExclusiveLock; + if (!gtt_storage_attached(rid)) + continue; + } + else + lockmode = AccessExclusiveLock; - rel = table_open(rid, AccessExclusiveLock); + rel = table_open(rid, lockmode); relations = lappend(relations, rel); + lock_modes = lappend_int(lock_modes, lockmode); } /* Don't allow truncate on tables that are referenced by foreign keys */ heap_truncate_check_FKs(relations, true); /* OK to do it */ - foreach(cell, relations) + forboth(cell_rel, relations, cell_lock, lock_modes) { - Relation rel = lfirst(cell); + Relation rel = lfirst(cell_rel); + LOCKMODE lockmode = lfirst_int(cell_lock); /* Truncate the relation */ - heap_truncate_one_rel(rel); + heap_truncate_one_rel(rel, lockmode); /* Close the relation, but keep exclusive lock on it until commit */ table_close(rel, NoLock); @@ -3361,7 +3419,7 @@ heap_truncate(List *relids) * checked permissions etc, and must have obtained AccessExclusiveLock. */ void -heap_truncate_one_rel(Relation rel) +heap_truncate_one_rel(Relation rel, LOCKMODE lockmode) { Oid toastrelid; @@ -3376,16 +3434,16 @@ heap_truncate_one_rel(Relation rel) table_relation_nontransactional_truncate(rel); /* If the relation has indexes, truncate the indexes too */ - RelationTruncateIndexes(rel); + RelationTruncateIndexes(rel, lockmode); /* If there is a toast table, truncate that too */ toastrelid = rel->rd_rel->reltoastrelid; if (OidIsValid(toastrelid)) { - Relation toastrel = table_open(toastrelid, AccessExclusiveLock); + Relation toastrel = table_open(toastrelid, lockmode); table_relation_nontransactional_truncate(toastrel); - RelationTruncateIndexes(toastrel); + RelationTruncateIndexes(toastrel, lockmode); /* keep the lock... */ table_close(toastrel, NoLock); } @@ -3856,3 +3914,15 @@ StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound) CacheInvalidateRelcache(parent); } + +void +CheckGlobalTempTableNotInUse(Relation rel, const char *stmt) +{ + if (RELATION_IS_GLOBAL_TEMP(rel) && + is_other_backend_use_gtt(RelationGetRelid(rel))) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_IN_USE), + errmsg("cannot %s \"%s\" because it is being used by other session", + stmt, RelationGetRelationName(rel)), + errdetail("Please try detach all other sessions using this table and try again."))); +} diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index e4203819a0e..17b79212365 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -54,6 +54,7 @@ #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/event_trigger.h" #include "commands/progress.h" #include "commands/tablecmds.h" @@ -125,7 +126,8 @@ static void UpdateIndexRelation(Oid indexoid, Oid heapoid, bool isready); static void index_update_stats(Relation rel, bool hasindex, - double reltuples); + double reltuples, + bool isreindex); static void IndexCheckExclusion(Relation heapRelation, Relation indexRelation, IndexInfo *indexInfo); @@ -736,6 +738,21 @@ index_create(Relation heapRelation, MultiXactId relminmxid; bool create_storage = !OidIsValid(relFileNode); + if (RELATION_IS_GLOBAL_TEMP(heapRelation)) + { + /* disable create index on global temporary table with concurrent mode */ + concurrent = false; + + /* + * For the case that some backend is applied relcache message to create + * an index on a global temporary table, if this table in the current + * backend are not initialized, the creation of index storage on the + * table are also skipped. + */ + if (!gtt_storage_attached(RelationGetRelid(heapRelation))) + flags |= INDEX_CREATE_SKIP_BUILD; + } + /* constraint flags can only be set when a constraint is requested */ Assert((constr_flags == 0) || ((flags & INDEX_CREATE_ADD_CONSTRAINT) != 0)); @@ -1242,7 +1259,8 @@ index_create(Relation heapRelation, */ index_update_stats(heapRelation, true, - -1.0); + -1.0, + false); /* Make the above update visible */ CommandCounterIncrement(); } @@ -2138,7 +2156,7 @@ 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(!RelpersistenceTsTemp(get_rel_persistence(indexId)) || (!concurrent && !concurrent_lock_mode)); /* @@ -2170,6 +2188,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) */ CheckTableNotInUse(userIndexRelation, "DROP INDEX"); + /* + * Allow to drop index on global temporary table when only current + * backend use it. + */ + if (RELATION_IS_GLOBAL_TEMP(userHeapRelation)) + CheckGlobalTempTableNotInUse(userHeapRelation, + "DROP INDEX ON GLOBAL TEMPORARY TABLE"); + /* * Drop Index Concurrently is more or less the reverse process of Create * Index Concurrently. @@ -2771,7 +2797,8 @@ FormIndexDatum(IndexInfo *indexInfo, static void index_update_stats(Relation rel, bool hasindex, - double reltuples) + double reltuples, + bool isreindex) { Oid relid = RelationGetRelid(rel); Relation pg_class; @@ -2779,6 +2806,13 @@ index_update_stats(Relation rel, Form_pg_class rd_rel; bool dirty; + /* + * Most of the global Temp table data is updated to the local hash, and reindex + * does not refresh relcache, so call a separate function. + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + return index_update_gtt_relstats(rel, hasindex, reltuples, isreindex); + /* * We always update the pg_class row using a non-transactional, * overwrite-in-place update. There are several reasons for this: @@ -2998,6 +3032,25 @@ index_build(Relation heapRelation, pgstat_progress_update_multi_param(6, progress_index, progress_vals); } + /* For build index on global temporary table */ + if (RELATION_IS_GLOBAL_TEMP(indexRelation)) + { + /* + * If the storage for the index in this session is not initialized, + * it needs to be created. + */ + if (!gtt_storage_attached(RelationGetRelid(indexRelation))) + { + /* Before create init storage, fix the local Relcache first */ + force_enable_gtt_index(indexRelation); + + Assert(gtt_storage_attached(RelationGetRelid(heapRelation))); + + /* Init storage for index */ + RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation); + } + } + /* * Call the access method's build procedure */ @@ -3080,11 +3133,13 @@ index_build(Relation heapRelation, */ index_update_stats(heapRelation, true, - stats->heap_tuples); + stats->heap_tuples, + isreindex); index_update_stats(indexRelation, false, - stats->index_tuples); + stats->index_tuples, + isreindex); /* Make the updated catalog row versions visible */ CommandCounterIncrement(); @@ -3539,6 +3594,8 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, PGRUsage ru0; bool progress = ((params->options & REINDEXOPT_REPORT_PROGRESS) != 0); bool set_tablespace = false; + LOCKMODE lockmode_on_heap = ShareLock; + LOCKMODE lockmode_on_index = AccessExclusiveLock; pg_rusage_init(&ru0); @@ -3552,10 +3609,35 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, if (!OidIsValid(heapId)) return; + /* + * For reindex on global temporary table, If the storage for the index + * in current session is not initialized, nothing is done. + */ + if (persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + if (OidIsValid(params->tablespaceOid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change tablespace of global temporary table"))); + + if (!gtt_storage_attached(indexId)) + { + /* Suppress use of the target index while rebuilding it */ + SetReindexProcessing(heapId, indexId); + /* Re-allow use of target index */ + ResetReindexProcessing(); + return; + } + + /* For global temp table reindex handles local data, using low-level locks */ + lockmode_on_heap = AccessShareLock; + lockmode_on_index = AccessShareLock; + } + if ((params->options & REINDEXOPT_MISSING_OK) != 0) - heapRelation = try_table_open(heapId, ShareLock); + heapRelation = try_table_open(heapId, lockmode_on_heap); else - heapRelation = table_open(heapId, ShareLock); + heapRelation = table_open(heapId, lockmode_on_heap); /* if relation is gone, leave */ if (!heapRelation) @@ -3581,7 +3663,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * Open the target index relation and get an exclusive lock on it, to * ensure that no one else is touching this particular index. */ - iRel = index_open(indexId, AccessExclusiveLock); + iRel = index_open(indexId, lockmode_on_index); if (progress) pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID, @@ -3823,7 +3905,7 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, * index rebuild. */ bool -reindex_relation(Oid relid, int flags, ReindexParams *params) +reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode) { Relation rel; Oid toast_relid; @@ -3839,9 +3921,9 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) * should match ReindexTable(). */ if ((params->options & REINDEXOPT_MISSING_OK) != 0) - rel = try_table_open(relid, ShareLock); + rel = try_table_open(relid, lockmode); else - rel = table_open(relid, ShareLock); + rel = table_open(relid, lockmode); /* if relation is gone, leave */ if (!rel) @@ -3948,7 +4030,7 @@ reindex_relation(Oid relid, int flags, ReindexParams *params) newparams.options &= ~(REINDEXOPT_MISSING_OK); newparams.tablespaceOid = InvalidOid; - result |= reindex_relation(toast_relid, flags, &newparams); + result |= reindex_relation(toast_relid, flags, &newparams, lockmode); } return result; diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 5dbac9c437a..033fef26fe3 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -656,6 +656,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) errmsg("cannot create temporary relation in non-temporary schema"))); } break; + case RELPERSISTENCE_GLOBAL_TEMP: + /* Do not allow create global temporary table in temporary schemas */ + if (isAnyTempNamespace(nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot create global temporary relation in temporary schema"))); + break; case RELPERSISTENCE_PERMANENT: if (isTempOrTempToastNamespace(nspid)) newRelation->relpersistence = RELPERSISTENCE_TEMP; diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c index 9b8075536a7..733d89c0e8a 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -27,10 +27,12 @@ #include "access/xlogutils.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "storage/freespace.h" #include "storage/smgr.h" #include "utils/hsearch.h" +#include "utils/inval.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -61,6 +63,7 @@ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ + Oid temprelOid; /* InvalidOid if not a global temporary rel */ bool atCommit; /* T=delete at commit; F=delete at abort */ int nestLevel; /* xact nesting level of request */ struct PendingRelDelete *next; /* linked-list link */ @@ -115,7 +118,7 @@ AddPendingSync(const RelFileNode *rnode) * transaction aborts later on, the storage will be destroyed. */ SMgrRelation -RelationCreateStorage(RelFileNode rnode, char relpersistence) +RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel) { PendingRelDelete *pending; SMgrRelation srel; @@ -126,7 +129,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) switch (relpersistence) { + /* + * global temporary table and local temporary table use same + * design on storage module. + */ case RELPERSISTENCE_TEMP: + case RELPERSISTENCE_GLOBAL_TEMP: backend = BackendIdForTempRelations(); needs_wal = false; break; @@ -154,6 +162,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rnode; pending->backend = backend; + pending->temprelOid = InvalidOid; pending->atCommit = false; /* delete if abort */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; @@ -165,6 +174,30 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) AddPendingSync(&rnode); } + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + Assert(rel && RELATION_IS_GLOBAL_TEMP(rel)); + + /* + * Remember the reloid of global temporary table, which is used for + * transaction commit or rollback. + * see smgrDoPendingDeletes. + */ + pending->temprelOid = RelationGetRelid(rel); + + /* Remember global temporary table storage info to localhash */ + remember_gtt_storage_info(rnode, rel); + + /* Make cache invalid and set new relnode to local relcache. */ + CacheInvalidateRelcache(rel); + + /* + * Make the pg_class row change or relation map change visible. This will + * cause the relcache entry to get updated, too. + */ + CommandCounterIncrement(); + } + return srel; } @@ -201,11 +234,20 @@ RelationDropStorage(Relation rel) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; + pending->temprelOid = InvalidOid; pending->atCommit = true; /* delete if commit */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + /* + * Remember the reloid of global temporary table, which is used for + * transaction commit or rollback. + * see smgrDoPendingDeletes. + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + pending->temprelOid = RelationGetRelid(rel); + /* * NOTE: if the relation was created in this transaction, it will now be * present in the pending-delete list twice, once with atCommit true and @@ -618,6 +660,7 @@ smgrDoPendingDeletes(bool isCommit) int nrels = 0, maxrels = 0; SMgrRelation *srels = NULL; + Oid *reloids = NULL; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -647,14 +690,18 @@ smgrDoPendingDeletes(bool isCommit) { maxrels = 8; srels = palloc(sizeof(SMgrRelation) * maxrels); + reloids = palloc(sizeof(Oid) * maxrels); } else if (maxrels <= nrels) { maxrels *= 2; srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + reloids = repalloc(reloids, sizeof(Oid) * maxrels); } - srels[nrels++] = srel; + srels[nrels] = srel; + reloids[nrels] = pending->temprelOid; + nrels++; } /* must explicitly free the list entry */ pfree(pending); @@ -664,12 +711,21 @@ smgrDoPendingDeletes(bool isCommit) if (nrels > 0) { + int i; + smgrdounlinkall(srels, nrels, false); - for (int i = 0; i < nrels; i++) + for (i = 0; i < nrels; i++) + { smgrclose(srels[i]); + /* free global temporary table info in localhash */ + if (gtt_storage_attached(reloids[i])) + forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit); + } + pfree(srels); + pfree(reloids); } } diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c new file mode 100644 index 00000000000..ea349a78072 --- /dev/null +++ b/src/backend/catalog/storage_gtt.c @@ -0,0 +1,1669 @@ +/*------------------------------------------------------------------------- + * + * storage_gtt.c + * The body implementation of Global Temparary table. + * + * IDENTIFICATION + * src/backend/catalog/storage_gtt.c + * + * See src/backend/catalog/GTT_README for Global temparary table's + * requirements and design. + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/amapi.h" +#include "access/heapam.h" +#include "access/multixact.h" +#include "access/visibilitymap.h" +#include "catalog/catalog.h" +#include "catalog/index.h" +#include "catalog/pg_statistic.h" +#include "catalog/storage.h" +#include "catalog/storage_gtt.h" +#include "commands/tablecmds.h" +#include "funcapi.h" +#include "miscadmin.h" +#include "nodes/pg_list.h" +#include "storage/bufmgr.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/sinvaladt.h" +#include "utils/catcache.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/syscache.h" + +/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */ +#define WORDNUM(x) ((x) / BITS_PER_BITMAPWORD) +#define BITNUM(x) ((x) % BITS_PER_BITMAPWORD) + +#define BITMAPSET_SIZE(nwords) \ + (offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword)) + +static bool gtt_cleaner_exit_registered = false; +static HTAB *gtt_storage_local_hash = NULL; +static HTAB *active_gtt_shared_hash = NULL; +static MemoryContext gtt_info_context = NULL; + +/* relfrozenxid of all gtts in the current session */ +static List *gtt_session_relfrozenxid_list = NIL; + +int vacuum_gtt_defer_check_age = 0; + +/* + * The Global temporary table's shared hash table data structure + */ +typedef struct gtt_ctl_data +{ + LWLock lock; + int max_entry; + int entry_size; +}gtt_ctl_data; + +static gtt_ctl_data *gtt_shared_ctl = NULL; + +typedef struct gtt_fnode +{ + Oid dbNode; + Oid relNode; +} gtt_fnode; + +/* record this global temporary table in which backends are being used */ +typedef struct +{ + gtt_fnode rnode; + Bitmapset *map; + /* bitmap data */ +} gtt_shared_hash_entry; + +/* + * The Global temporary table's local hash table data structure + */ +/* Record the storage information and statistical information of the global temporary table */ +typedef struct +{ + Oid relfilenode; + Oid spcnode; + + /* pg_class relstat */ + int32 relpages; + float4 reltuples; + int32 relallvisible; + TransactionId relfrozenxid; + TransactionId relminmxid; + + /* pg_statistic column stat */ + int natts; + int *attnum; + HeapTuple *att_stat_tups; +} gtt_relfilenode; + +typedef struct +{ + Oid relid; + + List *relfilenode_list; + + char relkind; + bool on_commit_delete; +} gtt_local_hash_entry; + +static Size action_gtt_shared_hash_entry_size(void); +static void gtt_storage_checkin(Oid relid); +static void gtt_storage_checkout(Oid relid, bool isCommit); +static void gtt_storage_removeall(int code, Datum arg); +static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid); +static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid); +static void set_gtt_session_relfrozenxid(void); +static void gtt_free_statistics(gtt_relfilenode *rnode); +static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, Oid spcnode, bool missing_ok); +static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok); +static Bitmapset *copy_active_gtt_bitmap(Oid relid); + +Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS); +Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS); +Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS); +Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS); + +/* + * Calculate shared hash table entry size for GTT. + */ +static Size +action_gtt_shared_hash_entry_size(void) +{ + int wordnum; + Size hash_entry_size = 0; + + if (max_active_gtt <= 0) + return 0; + + wordnum = WORDNUM(MaxBackends + 1); + /* hash entry header size */ + hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry)); + /* + * hash entry data size + * this is a bitmap in shared memory, each backend have a bit. + */ + hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1)); + + return hash_entry_size; +} + +/* + * Calculate shared hash table max size for GTT. + */ +Size +active_gtt_shared_hash_size(void) +{ + Size size = 0; + Size hash_entry_size = 0; + + if (max_active_gtt <= 0) + return 0; + + /* shared hash header size */ + size = MAXALIGN(sizeof(gtt_ctl_data)); + /* hash entry size */ + hash_entry_size = action_gtt_shared_hash_entry_size(); + /* max size */ + size += hash_estimate_size(max_active_gtt, hash_entry_size); + + return size; +} + +/* + * Initialization shared hash table for GTT. + */ +void +active_gtt_shared_hash_init(void) +{ + HASHCTL info; + bool found; + + if (max_active_gtt <= 0) + return; + + gtt_shared_ctl = + ShmemInitStruct("gtt_shared_ctl", + sizeof(gtt_ctl_data), + &found); + + if (!found) + { + LWLockRegisterTranche(LWTRANCHE_GLOBAL_TEMP_TABLE_CTL, "gtt_shared_ctl"); + LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GLOBAL_TEMP_TABLE_CTL); + gtt_shared_ctl->max_entry = max_active_gtt; + gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size(); + } + + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(gtt_fnode); + info.entrysize = action_gtt_shared_hash_entry_size(); + active_gtt_shared_hash = + ShmemInitHash("active gtt shared hash", + gtt_shared_ctl->max_entry, + gtt_shared_ctl->max_entry, + &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE); +} + +/* + * Record GTT relid to shared hash table, which means that current session is using this GTT. + */ +static void +gtt_storage_checkin(Oid relid) +{ + gtt_shared_hash_entry *entry; + bool found; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + entry = hash_search(active_gtt_shared_hash, + (void *)&(fnode), HASH_ENTER_NULL, &found); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + ereport(ERROR, + (errcode(ERRCODE_OUT_OF_MEMORY), + errmsg("out of shared memory"), + errhint("You might need to increase max_active_global_temporary_table."))); + } + + if (!found) + { + int wordnum; + + /* init bitmap */ + entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry))); + wordnum = WORDNUM(MaxBackends + 1); + memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1)); + entry->map->nwords = wordnum + 1; + } + + /* record itself in bitmap */ + bms_add_member(entry->map, MyBackendId); + LWLockRelease(>t_shared_ctl->lock); +} + +/* + * Remove the GTT relid record from the shared hash table which means that current session is + * not use this GTT. + */ +static void +gtt_storage_checkout(Oid relid, bool isCommit) +{ + gtt_shared_hash_entry *entry; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + if (isCommit) + elog(WARNING, "relid %u not exist in gtt shared hash when drop local storage", relid); + + return; + } + + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + + /* remove itself from bitmap */ + bms_del_member(entry->map, MyBackendId); + if (bms_is_empty(entry->map)) + { + if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL)) + elog(PANIC, "gtt shared hash table corrupted"); + } + LWLockRelease(>t_shared_ctl->lock); + + return; +} + +/* + * Gets usage information for a GTT from shared hash table. + * The information is in the form of bitmap. + * Quickly copy the entire bitmap from shared memory and return it. + * that to avoid holding locks for a long time. + */ +static Bitmapset * +copy_active_gtt_bitmap(Oid relid) +{ + gtt_shared_hash_entry *entry; + Bitmapset *map_copy = NULL; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return NULL; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry) + { + Assert(entry->map); + /* copy the entire bitmap */ + if (!bms_is_empty(entry->map)) + map_copy = bms_copy(entry->map); + } + + LWLockRelease(>t_shared_ctl->lock); + + return map_copy; +} + +/* + * Check if there are other sessions using this GTT besides the current session. + */ +bool +is_other_backend_use_gtt(Oid relid) +{ + gtt_shared_hash_entry *entry; + bool in_use = false; + int num_use = 0; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return false; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + return false; + } + + Assert(entry->map); + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + + /* how many backend are using this GTT */ + num_use = bms_num_members(entry->map); + if (num_use == 0) + in_use = false; + else if (num_use == 1) + { + /* check if this is itself */ + if(bms_is_member(MyBackendId, entry->map)) + in_use = false; + else + in_use = true; + } + else + in_use = true; + + LWLockRelease(>t_shared_ctl->lock); + + return in_use; +} + +/* + * Record GTT information to local hash. + * They include GTT storage info, transaction info and statistical info. + */ +void +remember_gtt_storage_info(RelFileNode rnode, Relation rel) +{ + gtt_local_hash_entry *entry; + MemoryContext oldcontext; + gtt_relfilenode *new_node = NULL; + Oid relid = RelationGetRelid(rel); + int natts = 0; + + if (max_active_gtt <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Global temporary table feature is disable"), + errhint("You might need to increase max_active_global_temporary_table to enable this feature."))); + + if (RecoveryInProgress()) + elog(ERROR, "readonly mode not support access global temporary table"); + + if (rel->rd_rel->relkind == RELKIND_INDEX && + rel->rd_index && + (!rel->rd_index->indisvalid || + !rel->rd_index->indisready || + !rel->rd_index->indislive)) + elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel)); + + /* First time through: initialize the hash table */ + if (gtt_storage_local_hash == NULL) + { +#define GTT_LOCAL_HASH_SIZE 1024 + HASHCTL ctl; + + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + gtt_info_context = + AllocSetContextCreate(CacheMemoryContext, + "gtt info context", + ALLOCSET_DEFAULT_SIZES); + + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(gtt_local_hash_entry); + ctl.hcxt = gtt_info_context; + gtt_storage_local_hash = + hash_create("global temporary table info", + GTT_LOCAL_HASH_SIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + } + + Assert(CacheMemoryContext); + Assert(gtt_info_context); + oldcontext = MemoryContextSwitchTo(gtt_info_context); + + entry = gtt_search_by_relid(relid, true); + if (!entry) + { + bool found = false; + + /* Look up or create an entry */ + entry = hash_search(gtt_storage_local_hash, + (void *) &relid, HASH_ENTER, &found); + + if (found) + { + MemoryContextSwitchTo(oldcontext); + elog(ERROR, "backend %d relid %u already exists in gtt local hash", + MyBackendId, relid); + } + + entry->relfilenode_list = NIL; + entry->relkind = rel->rd_rel->relkind; + entry->on_commit_delete = false; + + if (entry->relkind == RELKIND_RELATION) + { + /* record the on commit clause */ + if (RELATION_GTT_ON_COMMIT_DELETE(rel)) + { + entry->on_commit_delete = true; + register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS, true); + } + } + + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + { + gtt_storage_checkin(relid); + } + } + + /* record storage info relstat columnstats and transaction info to relfilenode list */ + new_node = palloc0(sizeof(gtt_relfilenode)); + new_node->relfilenode = rnode.relNode; + new_node->spcnode = rnode.spcNode; + new_node->relpages = 0; + new_node->reltuples = 0; + new_node->relallvisible = 0; + new_node->relfrozenxid = InvalidTransactionId; + new_node->relminmxid = InvalidMultiXactId; + new_node->natts = 0; + new_node->attnum = NULL; + new_node->att_stat_tups = NULL; + entry->relfilenode_list = lappend(entry->relfilenode_list, new_node); + + /* init column stats structure */ + natts = RelationGetNumberOfAttributes(rel); + new_node->attnum = palloc0(sizeof(int) * natts); + new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts); + new_node->natts = natts; + + /* remember transaction info */ + if (RELKIND_HAS_TABLE_AM(entry->relkind)) + { + new_node->relfrozenxid = RecentXmin; + new_node->relminmxid = GetOldestMultiXactId(); + + insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + MemoryContextSwitchTo(oldcontext); + + /* Registration callbacks are used to trigger cleanup during process exit */ + if (!gtt_cleaner_exit_registered) + { + before_shmem_exit(gtt_storage_removeall, 0); + gtt_cleaner_exit_registered = true; + } + + return; +} + +/* + * Remove GTT information from local hash when transaction commit/rollback. + */ +void +forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit) +{ + gtt_local_hash_entry *entry = NULL; + gtt_relfilenode *d_rnode = NULL; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + { + if (isCommit) + elog(ERROR,"gtt rel %u not found in local hash", relid); + + return; + } + + d_rnode = gtt_search_relfilenode(entry, rnode.relNode, rnode.spcNode, true); + if (d_rnode == NULL) + { + if (isCommit) + elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid); + else + { + /* rollback transaction */ + if (entry->relfilenode_list == NIL) + { + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, isCommit); + + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + + return; + } + } + + /* Clean up transaction info from Local order list and MyProc */ + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_TOASTVALUE) + { + Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit); + + /* this is valid relfrozenxid */ + if (TransactionIdIsValid(d_rnode->relfrozenxid)) + { + remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + } + + /* delete relfilenode from rel entry */ + entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode); + gtt_free_statistics(d_rnode); + + if (entry->relfilenode_list == NIL) + { + /* tell shared hash that current session will no longer use this GTT */ + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, isCommit); + + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + + return; +} + +/* + * Check if current session is using this GTT. + */ +bool +gtt_storage_attached(Oid relid) +{ + bool found = false; + gtt_local_hash_entry *entry = NULL; + + if (max_active_gtt <= 0) + return false; + + if (!OidIsValid(relid)) + return false; + + entry = gtt_search_by_relid(relid, true); + if (entry) + found = true; + + return found; +} + +/* + * When backend exit, bulk cleaning all GTT storage and local buffer of this backend. + */ +static void +gtt_storage_removeall(int code, Datum arg) +{ + HASH_SEQ_STATUS status; + gtt_local_hash_entry *entry; + + if (gtt_storage_local_hash == NULL) + return; + + /* Need to ensure we have a usable transaction. */ + AbortOutOfAnyTransaction(); + + /* Search all relfilenode for GTT in current session */ + hash_seq_init(&status, gtt_storage_local_hash); + while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL) + { + ListCell *lc; + + foreach(lc, entry->relfilenode_list) + { + SMgrRelation srel[1]; + RelFileNode rnode; + gtt_relfilenode *gtt_rnode = lfirst(lc); + + rnode.spcNode = gtt_rnode->spcnode; + rnode.dbNode = MyDatabaseId; + rnode.relNode = gtt_rnode->relfilenode; + srel[0] = smgropen(rnode, MyBackendId); + smgrdounlinkall(srel, 1, false); + smgrclose(srel[0]); + } + + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(entry->relid, false); + + hash_search(gtt_storage_local_hash, (void *) &(entry->relid), HASH_REMOVE, NULL); + } + + /* set to global area */ + MyProc->gtt_frozenxid = InvalidTransactionId; + + return; +} + +/* + * Update GTT relstats(relpage/reltuple/relallvisible) + * to local hash. + */ +void +gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples, + BlockNumber relallvisible, TransactionId relfrozenxid, + TransactionId relminmxid) +{ + Oid relid = RelationGetRelid(relation); + gtt_relfilenode *gtt_rnode = NULL; + gtt_local_hash_entry *entry = NULL; + + if (max_active_gtt <= 0) + return; + + if (!OidIsValid(relid)) + return; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return; + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return; + + if (relpages > 0 && + gtt_rnode->relpages != (int32)relpages) + { + gtt_rnode->relpages = (int32)relpages; + relation->rd_rel->relpages = (int32) relpages; + } + + if (reltuples > 0 && + gtt_rnode->reltuples != (float4)reltuples) + { + gtt_rnode->reltuples = (float4)reltuples; + relation->rd_rel->reltuples = (float4)reltuples; + } + + /* only heap contain transaction information and relallvisible */ + if (RELKIND_HAS_TABLE_AM(entry->relkind)) + { + if (relallvisible > 0 && + gtt_rnode->relallvisible != (int32)relallvisible) + { + gtt_rnode->relallvisible = (int32)relallvisible; + relation->rd_rel->relallvisible = (int32)relallvisible; + } + + if (TransactionIdIsNormal(relfrozenxid) && + gtt_rnode->relfrozenxid != relfrozenxid && + (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) || + TransactionIdPrecedes(ReadNextTransactionId(), gtt_rnode->relfrozenxid))) + { + /* set to local order list */ + remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid); + gtt_rnode->relfrozenxid = relfrozenxid; + insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid); + /* set to global area */ + set_gtt_session_relfrozenxid(); + relation->rd_rel->relfrozenxid = relfrozenxid; + } + + if (MultiXactIdIsValid(relminmxid) && + gtt_rnode->relminmxid != relminmxid && + (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) || + MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid))) + { + gtt_rnode->relminmxid = relminmxid; + relation->rd_rel->relminmxid = relminmxid; + } + } + + return; +} + +/* + * Search GTT relstats(relpage/reltuple/relallvisible) + * from local has. + */ +bool +get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples, + BlockNumber *relallvisible, TransactionId *relfrozenxid, + TransactionId *relminmxid) +{ + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + return false; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return false; + + Assert(entry->relid == relid); + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return false; + + if (relpages) + *relpages = gtt_rnode->relpages; + + if (reltuples) + *reltuples = gtt_rnode->reltuples; + + if (relallvisible) + *relallvisible = gtt_rnode->relallvisible; + + if (relfrozenxid) + *relfrozenxid = gtt_rnode->relfrozenxid; + + if (relminmxid) + *relminmxid = gtt_rnode->relminmxid; + + return true; +} + +/* + * Update GTT info(definition is same as pg_statistic) + * to local hash. + */ +void +up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts, + TupleDesc tupleDescriptor, Datum *values, bool *isnull) +{ + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + MemoryContext oldcontext; + bool found = false; + int i = 0; + + /* not support whole row or system column */ + if (attnum <= 0) + return; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return; + + Assert(entry->relid == reloid); + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return; + + /* switch context to gtt_info_context for store tuple at heap_form_tuple */ + oldcontext = MemoryContextSwitchTo(gtt_info_context); + for (i = 0; i < gtt_rnode->natts; i++) + { + if (gtt_rnode->attnum[i] == 0) + { + Assert(gtt_rnode->att_stat_tups[i] == NULL); + gtt_rnode->attnum[i] = attnum; + gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull); + found = true; + break; + } + else if (gtt_rnode->attnum[i] == attnum) + { + Assert(gtt_rnode->att_stat_tups[i]); + heap_freetuple(gtt_rnode->att_stat_tups[i]); + gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull); + found = true; + break; + } + } + MemoryContextSwitchTo(oldcontext); + + if (!found) + elog(WARNING, "analyze can not update relid %u column %d statistics after add or drop column, try truncate table first", reloid, attnum); + + return; +} + +/* + * Search GTT statistic info(definition is same as pg_statistic) + * from local hash. + */ +HeapTuple +get_gtt_att_statistic(Oid reloid, int attnum, bool inh) +{ + gtt_local_hash_entry *entry; + int i = 0; + gtt_relfilenode *gtt_rnode = NULL; + + /* not support whole row or system column */ + if (attnum <= 0) + return NULL; + + if (max_active_gtt <= 0) + return NULL; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return NULL; + + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return NULL; + + for (i = 0; i < gtt_rnode->natts; i++) + { + if (gtt_rnode->attnum[i] == attnum) + { + Assert(gtt_rnode->att_stat_tups[i]); + return gtt_rnode->att_stat_tups[i]; + } + } + + return NULL; +} + +void +release_gtt_statistic_cache(HeapTuple tup) +{ + /* do nothing */ + return; +} + +/* + * Maintain a order relfrozenxid list of backend Level for GTT. + * Insert a RelfrozenXID into the list and keep the list in order. + */ +static void +insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid) +{ + MemoryContext oldcontext; + ListCell *cell; + int i; + + Assert(TransactionIdIsNormal(relfrozenxid)); + + oldcontext = MemoryContextSwitchTo(gtt_info_context); + + /* Does the datum belong at the front? */ + if (gtt_session_relfrozenxid_list == NIL || + TransactionIdFollowsOrEquals(relfrozenxid, + linitial_oid(gtt_session_relfrozenxid_list))) + { + gtt_session_relfrozenxid_list = + lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list); + MemoryContextSwitchTo(oldcontext); + + return; + } + + /* No, so find the entry it belongs after */ + i = 0; + foreach (cell, gtt_session_relfrozenxid_list) + { + if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell))) + break; + + i++; + } + gtt_session_relfrozenxid_list = + list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid); + + MemoryContextSwitchTo(oldcontext); + + return; +} + +/* + * Maintain a order relfrozenxid list of backend Level for GTT. + * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list. + */ +static void +remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid) +{ + gtt_session_relfrozenxid_list = + list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid); +} + +/* + * Update of backend Level oldest relfrozenxid to MyProc. + * This makes each backend's oldest RelFrozenxID globally visible. + */ +static void +set_gtt_session_relfrozenxid(void) +{ + TransactionId gtt_frozenxid = InvalidTransactionId; + + if (gtt_session_relfrozenxid_list != NIL) + gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list); + + if (MyProc->gtt_frozenxid != gtt_frozenxid) + MyProc->gtt_frozenxid = gtt_frozenxid; +} + +/* + * Get GTT column level data statistics. + */ +Datum +pg_get_gtt_statistics(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + HeapTuple tuple; + Relation rel = NULL; + Oid reloid = PG_GETARG_OID(0); + int attnum = PG_GETARG_INT32(1); + TupleDesc tupdesc; + MemoryContext oldcontext; + Relation pg_tatistic = NULL; + TupleDesc sd; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not " \ + "allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + if (!RELATION_IS_GLOBAL_TEMP(rel)) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + pg_tatistic = relation_open(StatisticRelationId, AccessShareLock); + sd = RelationGetDescr(pg_tatistic); + + /* get data from local hash */ + tuple = get_gtt_att_statistic(reloid, attnum, false); + if (tuple) + { + Datum values[Natts_pg_statistic]; + bool isnull[Natts_pg_statistic]; + HeapTuple res = NULL; + + memset(&values, 0, sizeof(values)); + memset(&isnull, 0, sizeof(isnull)); + heap_deform_tuple(tuple, sd, values, isnull); + res = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, res); + } + tuplestore_donestoring(tupstore); + + relation_close(rel, AccessShareLock); + relation_close(pg_tatistic, AccessShareLock); + + return (Datum) 0; +} + +/* + * Get GTT table level data statistics. + */ +Datum +pg_get_gtt_relstats(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + TupleDesc tupdesc; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + Oid relnode = 0; + BlockNumber relpages = 0; + BlockNumber relallvisible = 0; + uint32 relfrozenxid = 0; + uint32 relminmxid = 0; + double reltuples = 0; + Relation rel = NULL; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + if (!RELATION_IS_GLOBAL_TEMP(rel)) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + get_gtt_relstats(reloid, + &relpages, &reltuples, &relallvisible, + &relfrozenxid, &relminmxid); + relnode = gtt_fetch_current_relfilenode(reloid); + if (relnode != InvalidOid) + { + Datum values[6]; + bool isnull[6]; + + memset(isnull, 0, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = UInt32GetDatum(relnode); + values[1] = Int32GetDatum(relpages); + values[2] = Float4GetDatum((float4)reltuples); + values[3] = Int32GetDatum(relallvisible); + values[4] = UInt32GetDatum(relfrozenxid); + values[5] = UInt32GetDatum(relminmxid); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + tuplestore_donestoring(tupstore); + + relation_close(rel, NoLock); + + return (Datum) 0; +} + +/* + * Get a list of backend pids that are currently using this GTT. + */ +Datum +pg_gtt_attached_pid(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + PGPROC *proc = NULL; + Bitmapset *map = NULL; + Tuplestorestate *tupstore; + TupleDesc tupdesc; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + Relation rel = NULL; + int backendid = 0; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + rel = relation_open(reloid, AccessShareLock); + if (!RELATION_IS_GLOBAL_TEMP(rel)) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + /* get data from share hash */ + map = copy_active_gtt_bitmap(reloid); + if (map) + { + backendid = bms_first_member(map); + + do + { + /* backendid map to process pid */ + proc = BackendIdGetProc(backendid); + if (proc && proc->pid > 0) + { + Datum values[2]; + bool isnull[2]; + pid_t pid = proc->pid; + + memset(isnull, 0, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = UInt32GetDatum(reloid); + values[1] = Int32GetDatum(pid); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + backendid = bms_next_member(map, backendid); + } while (backendid > 0); + + pfree(map); + } + + tuplestore_donestoring(tupstore); + relation_close(rel, AccessShareLock); + + return (Datum) 0; +} + +/* + * Get backend level oldest relfrozenxid of each backend using GTT in current database. + */ +Datum +pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + Tuplestorestate *tupstore; + TupleDesc tupdesc; + MemoryContext oldcontext; + TransactionId oldest = InvalidTransactionId; + List *pids = NULL; + List *xids = NULL; + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + oldcontext = MemoryContextSwitchTo( + rsinfo->econtext->ecxt_per_query_memory); + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + MemoryContextSwitchTo(oldcontext); + + if (max_active_gtt <= 0) + return (Datum) 0; + + if (RecoveryInProgress()) + return (Datum) 0; + + /* Get all session level oldest relfrozenxid that in current database use global temp table */ + oldest = gtt_get_oldest_frozenxids_in_current_database(&pids, &xids); + if (TransactionIdIsValid(oldest)) + { + HeapTuple tuple; + ListCell *lc1 = NULL; + ListCell *lc2 = NULL; + + /* Save db level oldest relfrozenxid */ + pids = lappend_int(pids, 0); + xids = lappend_oid(xids, oldest); + + Assert(list_length(pids) == list_length(xids)); + forboth(lc1, pids, lc2, xids) + { + Datum values[2]; + bool isnull[2]; + + memset(isnull, 0, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = Int32GetDatum(lfirst_int(lc1)); + values[1] = UInt32GetDatum(lfirst_oid(lc2)); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + } + tuplestore_donestoring(tupstore); + list_free(pids); + list_free(xids); + + return (Datum) 0; +} + +/* + * In order to build the GTT index, force enable GTT'index. + */ +void +force_enable_gtt_index(Relation index) +{ + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + Assert(index->rd_rel->relkind == RELKIND_INDEX); + Assert(OidIsValid(RelationGetRelid(index))); + + index->rd_index->indisvalid = true; + index->rd_index->indislive = true; + index->rd_index->indisready = true; +} + +/* + * Fix the local state of the GTT's index. + */ +void +gtt_correct_index_session_state(Relation index) +{ + Oid indexOid = RelationGetRelid(index); + Oid heapOid = index->rd_index->indrelid; + + /* Must be GTT */ + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + if (!index->rd_index->indisvalid) + return; + + /* + * If this GTT is not initialized in the current session, + * its index status is temporarily set to invalid(local relcache). + */ + if (gtt_storage_attached(heapOid) && + !gtt_storage_attached(indexOid)) + { + index->rd_index->indisvalid = false; + index->rd_index->indislive = false; + index->rd_index->indisready = false; + } + + return; +} + +/* + * Initialize storage of GTT and build empty index in this session. + */ +void +gtt_init_storage(CmdType operation, Relation relation) +{ + Oid toastrelid; + List *indexoidlist = NIL; + ListCell *l; + + if (!(operation == CMD_INSERT)) + return; + + if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + return; + + if (!RELATION_IS_GLOBAL_TEMP(relation)) + return; + + /* Each GTT is initialized once in each backend */ + if (gtt_storage_attached(RelationGetRelid(relation))) + return; + + /* init heap storage */ + RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation); + + indexoidlist = RelationGetIndexList(relation); + foreach(l, indexoidlist) + { + Oid indexOid = lfirst_oid(l); + Relation index = index_open(indexOid, RowExclusiveLock); + IndexInfo *info = BuildDummyIndexInfo(index); + + index_build(relation, index, info, true, false); + /* after build index, index re-enabled */ + Assert(index->rd_index->indisvalid); + Assert(index->rd_index->indislive); + Assert(index->rd_index->indisready); + index_close(index, NoLock); + } + list_free(indexoidlist); + + /* rebuild index for global temp toast table */ + toastrelid = relation->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + ListCell *indlist; + + toastrel = table_open(toastrelid, RowExclusiveLock); + + /* init index storage */ + RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel); + + foreach(indlist, RelationGetIndexList(toastrel)) + { + Oid indexId = lfirst_oid(indlist); + Relation index = index_open(indexId, RowExclusiveLock); + IndexInfo *info = BuildDummyIndexInfo(index); + + /* build empty index */ + index_build(toastrel, index, info, true, false); + Assert(index->rd_index->indisvalid); + Assert(index->rd_index->indislive); + Assert(index->rd_index->indisready); + index_close(index, NoLock); + } + + table_close(toastrel, NoLock); + } + + return; +} + +/* + * Release the data structure memory used to store GTT storage info. + */ +static void +gtt_free_statistics(gtt_relfilenode *rnode) +{ + int i; + + Assert(rnode); + + for (i = 0; i < rnode->natts; i++) + { + if (rnode->att_stat_tups[i]) + { + heap_freetuple(rnode->att_stat_tups[i]); + rnode->att_stat_tups[i] = NULL; + } + } + + if (rnode->attnum) + pfree(rnode->attnum); + + if (rnode->att_stat_tups) + pfree(rnode->att_stat_tups); + + pfree(rnode); + + return; +} + +/* + * Get the current relfilenode of this GTT. + */ +Oid +gtt_fetch_current_relfilenode(Oid relid) +{ + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + return InvalidOid; + + entry = gtt_search_by_relid(relid, true); + if (entry == NULL) + return InvalidOid; + + Assert(entry->relid == relid); + gtt_rnode = lfirst(list_tail(entry->relfilenode_list)); + if (gtt_rnode == NULL) + return InvalidOid; + + return gtt_rnode->relfilenode; +} + +/* + * Get a relfilenode used by this GTT during the transaction life cycle. + */ +static gtt_relfilenode * +gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, Oid spcnode, bool missing_ok) +{ + gtt_relfilenode *rnode = NULL; + ListCell *lc; + + Assert(entry); + + foreach(lc, entry->relfilenode_list) + { + gtt_relfilenode *gtt_rnode = lfirst(lc); + if (gtt_rnode->relfilenode == relfilenode && + gtt_rnode->spcnode == spcnode) + { + rnode = gtt_rnode; + break; + } + } + + if (!missing_ok && rnode == NULL) + elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid); + + return rnode; +} + +/* + * Get one GTT info from local hash. + */ +static gtt_local_hash_entry * +gtt_search_by_relid(Oid relid, bool missing_ok) +{ + gtt_local_hash_entry *entry = NULL; + + if (gtt_storage_local_hash == NULL) + return NULL; + + entry = hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_FIND, NULL); + + if (entry == NULL && !missing_ok) + elog(ERROR, "relid %u not found in local hash", relid); + + return entry; +} + +/* + * update pg_class entry after CREATE INDEX or REINDEX for global temp table + */ +void +index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex) +{ + Oid relid = RelationGetRelid(rel); + + Assert(RELATION_IS_GLOBAL_TEMP(rel)); + + /* see index_update_stats() */ + if (reltuples == 0 && rel->rd_rel->reltuples < 0) + reltuples = -1; + + /* update reltuples relpages relallvisible to localhash */ + if (reltuples >= 0) + { + BlockNumber relpages = RelationGetNumberOfBlocks(rel); + BlockNumber relallvisible = 0; + + if (rel->rd_rel->relkind != RELKIND_INDEX) + visibilitymap_count(rel, &relallvisible, NULL); + else + relallvisible = 0; + + gtt_update_relstats(rel, relpages, reltuples, relallvisible, + InvalidTransactionId, InvalidMultiXactId); + } + + /* update relhasindex to pg_class */ + if (hasindex != rel->rd_rel->relhasindex) + { + Relation pg_class = table_open(RelationRelationId, RowExclusiveLock); + Form_pg_class rd_rel; + HeapTuple tuple; + + Assert(rel->rd_rel->relkind != RELKIND_INDEX); + tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for relation %u", relid); + + rd_rel = (Form_pg_class) GETSTRUCT(tuple); + rd_rel->relhasindex = hasindex; + heap_inplace_update(pg_class, tuple); + heap_freetuple(tuple); + table_close(pg_class, RowExclusiveLock); + } + else if (!isreindex) + { + /* + * For global temp table + * Even if pg_class does not change, relcache needs to be rebuilt + * for flush rd_indexlist list (for a table) at create index process. + * + * Each session index has an independent data and cache(rd_amcache) + * so relcache of the table and index do not need to be refreshed at reindex process. + * This is different from the reindex of a regular table. + */ + CacheInvalidateRelcache(rel); + } +} + +/* + * update statistics for one global temp relation + */ +void +vac_update_gtt_relstats(Relation relation, + BlockNumber num_pages, double num_tuples, + BlockNumber num_all_visible_pages, + bool hasindex, TransactionId frozenxid, + MultiXactId minmulti, bool in_outer_xact) +{ + Oid relid = RelationGetRelid(relation); + Relation pg_class; + HeapTuple ctup; + Form_pg_class pgcform; + bool dirty = false; + List *idxs = NIL; + + Assert(RELATION_IS_GLOBAL_TEMP(relation)); + + /* For global temporary table, store relstats and transaction info to the localhash */ + gtt_update_relstats(relation, num_pages, num_tuples, + num_all_visible_pages, frozenxid, minmulti); + + if (relation->rd_rel->relkind == RELKIND_RELATION) + idxs = RelationGetIndexList(relation); + + pg_class = table_open(RelationRelationId, RowExclusiveLock); + + /* Fetch a copy of the tuple to scribble on */ + ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for relid %u vanished during vacuuming", + relid); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + /* Apply DDL updates, but not inside an outer transaction (see above) */ + if (!in_outer_xact) + { + /* + * If we didn't find any indexes, reset relhasindex. + * + * Global temporary table may contain indexes that are not valid locally. + * The catalog should not be updated based on local invalid index. + */ + if (pgcform->relhasindex && !hasindex && idxs == NIL) + { + pgcform->relhasindex = false; + dirty = true; + } + + /* We also clear relhasrules and relhastriggers if needed */ + if (pgcform->relhasrules && relation->rd_rules == NULL) + { + pgcform->relhasrules = false; + dirty = true; + } + if (pgcform->relhastriggers && relation->trigdesc == NULL) + { + pgcform->relhastriggers = false; + dirty = true; + } + } + + /* If anything changed, write out the tuple. */ + if (dirty) + heap_inplace_update(pg_class, ctup); + + table_close(pg_class, RowExclusiveLock); + + list_free(idxs); +} + +void +GlobalTempRelationSetNewRelfilenode(Relation relation) +{ + Oid newrelfilenode; + MultiXactId minmulti = InvalidMultiXactId; + TransactionId freezeXid = InvalidTransactionId; + RelFileNode newrnode; + + Assert(RELATION_IS_GLOBAL_TEMP(relation)); + Assert(!RelationIsMapped(relation)); + + /* Allocate a new relfilenode */ + newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL, + RELPERSISTENCE_GLOBAL_TEMP); + + /* + * Schedule unlinking of the old storage at transaction commit. + */ + RelationDropStorage(relation); + + newrnode = relation->rd_node; + newrnode.relNode = newrelfilenode; + + if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) + { + table_relation_set_new_filenode(relation, &newrnode, + RELPERSISTENCE_GLOBAL_TEMP, + &freezeXid, &minmulti); + } + else if (RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + { + /* handle these directly, at least for now */ + SMgrRelation srel; + + srel = RelationCreateStorage(newrnode, RELPERSISTENCE_GLOBAL_TEMP, relation); + smgrclose(srel); + } + else + { + /* we shouldn't be called for anything else */ + elog(ERROR, "relation \"%s\" does not have storage", + RelationGetRelationName(relation)); + } + + RelationAssumeNewRelfilenode(relation); + + /* The local relcache and hashtable have been updated */ + Assert(gtt_fetch_current_relfilenode(RelationGetRelid(relation)) == newrelfilenode); + Assert(relation->rd_node.relNode == newrelfilenode); +} diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 3cb69b1f87b..1625ee9345b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -186,6 +186,91 @@ CREATE VIEW pg_sequences AS WHERE NOT pg_is_other_temp_schema(N.oid) AND relkind = 'S'; +-- For global temporary table +CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS + SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.* + FROM + pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_get_gtt_relstats(c.oid) as s + WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + +CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS + SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.* + FROM + pg_class c + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_gtt_attached_pid(c.oid) as s + WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + +CREATE VIEW pg_gtt_stats WITH (security_barrier) AS +SELECT n.nspname AS schemaname, + c.relname AS tablename, + a.attname, + s.stainherit AS inherited, + s.stanullfrac AS null_frac, + s.stawidth AS avg_width, + s.stadistinct AS n_distinct, + CASE + WHEN s.stakind1 = 1 THEN s.stavalues1 + WHEN s.stakind2 = 1 THEN s.stavalues2 + WHEN s.stakind3 = 1 THEN s.stavalues3 + WHEN s.stakind4 = 1 THEN s.stavalues4 + WHEN s.stakind5 = 1 THEN s.stavalues5 + END AS most_common_vals, + CASE + WHEN s.stakind1 = 1 THEN s.stanumbers1 + WHEN s.stakind2 = 1 THEN s.stanumbers2 + WHEN s.stakind3 = 1 THEN s.stanumbers3 + WHEN s.stakind4 = 1 THEN s.stanumbers4 + WHEN s.stakind5 = 1 THEN s.stanumbers5 + END AS most_common_freqs, + CASE + WHEN s.stakind1 = 2 THEN s.stavalues1 + WHEN s.stakind2 = 2 THEN s.stavalues2 + WHEN s.stakind3 = 2 THEN s.stavalues3 + WHEN s.stakind4 = 2 THEN s.stavalues4 + WHEN s.stakind5 = 2 THEN s.stavalues5 + END AS histogram_bounds, + CASE + WHEN s.stakind1 = 3 THEN s.stanumbers1[1] + WHEN s.stakind2 = 3 THEN s.stanumbers2[1] + WHEN s.stakind3 = 3 THEN s.stanumbers3[1] + WHEN s.stakind4 = 3 THEN s.stanumbers4[1] + WHEN s.stakind5 = 3 THEN s.stanumbers5[1] + END AS correlation, + CASE + WHEN s.stakind1 = 4 THEN s.stavalues1 + WHEN s.stakind2 = 4 THEN s.stavalues2 + WHEN s.stakind3 = 4 THEN s.stavalues3 + WHEN s.stakind4 = 4 THEN s.stavalues4 + WHEN s.stakind5 = 4 THEN s.stavalues5 + END AS most_common_elems, + CASE + WHEN s.stakind1 = 4 THEN s.stanumbers1 + WHEN s.stakind2 = 4 THEN s.stanumbers2 + WHEN s.stakind3 = 4 THEN s.stanumbers3 + WHEN s.stakind4 = 4 THEN s.stanumbers4 + WHEN s.stakind5 = 4 THEN s.stanumbers5 + END AS most_common_elem_freqs, + CASE + WHEN s.stakind1 = 5 THEN s.stanumbers1 + WHEN s.stakind2 = 5 THEN s.stanumbers2 + WHEN s.stakind3 = 5 THEN s.stanumbers3 + WHEN s.stakind4 = 5 THEN s.stanumbers4 + WHEN s.stakind5 = 5 THEN s.stanumbers5 + END AS elem_count_histogram + FROM + pg_class c + JOIN pg_attribute a ON c.oid = a.attrelid + LEFT JOIN pg_namespace n ON n.oid = c.relnamespace, + pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s + WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid)); + CREATE VIEW pg_stats WITH (security_barrier) AS SELECT nspname AS schemaname, diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index a0da998c2ea..181bbb93f03 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -34,6 +34,7 @@ #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/storage_gtt.h" #include "commands/dbcommands.h" #include "commands/progress.h" #include "commands/tablecmds.h" @@ -105,7 +106,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel, HeapTuple *rows, int targrows, double *totalrows, double *totaldeadrows); static void update_attstats(Oid relid, bool inh, - int natts, VacAttrStats **vacattrstats); + int natts, VacAttrStats **vacattrstats, char relpersistence); static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull); @@ -186,6 +187,17 @@ analyze_rel(Oid relid, RangeVar *relation, return; } + /* + * Skip the global temporary table that did not initialize the storage + * in this backend. + */ + if (RELATION_IS_GLOBAL_TEMP(onerel) && + !gtt_storage_attached(RelationGetRelid(onerel))) + { + relation_close(onerel, ShareUpdateExclusiveLock); + return; + } + /* * We can ANALYZE any table except pg_statistic. See update_attstats */ @@ -602,14 +614,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params, * pg_statistic for columns we didn't process, we leave them alone.) */ update_attstats(RelationGetRelid(onerel), inh, - attr_cnt, vacattrstats); + attr_cnt, vacattrstats, RelationGetRelPersistence(onerel)); for (ind = 0; ind < nindexes; ind++) { AnlIndexData *thisdata = &indexdata[ind]; update_attstats(RelationGetRelid(Irel[ind]), false, - thisdata->attr_cnt, thisdata->vacattrstats); + thisdata->attr_cnt, thisdata->vacattrstats, + RelationGetRelPersistence(Irel[ind])); } /* Build extended statistics (if there are any). */ @@ -1617,7 +1630,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel, * by taking a self-exclusive lock on the relation in analyze_rel(). */ static void -update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) +update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence) { Relation sd; int attno; @@ -1719,31 +1732,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats) } } - /* Is there already a pg_statistic tuple for this attribute? */ - oldtup = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(relid), - Int16GetDatum(stats->attr->attnum), - BoolGetDatum(inh)); - - if (HeapTupleIsValid(oldtup)) + /* + * For global temporary table, + * Update column statistic to localhash, not pg_statistic. + */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { - /* Yes, replace it */ - stup = heap_modify_tuple(oldtup, - RelationGetDescr(sd), - values, - nulls, - replaces); - ReleaseSysCache(oldtup); - CatalogTupleUpdate(sd, &stup->t_self, stup); + up_gtt_att_statistic(relid, + stats->attr->attnum, + inh, + natts, + RelationGetDescr(sd), + values, + nulls); } else { - /* No, insert new tuple */ - stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); - CatalogTupleInsert(sd, stup); - } + /* Is there already a pg_statistic tuple for this attribute? */ + oldtup = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(relid), + Int16GetDatum(stats->attr->attnum), + BoolGetDatum(inh)); - heap_freetuple(stup); + if (HeapTupleIsValid(oldtup)) + { + /* Yes, replace it */ + stup = heap_modify_tuple(oldtup, + RelationGetDescr(sd), + values, + nulls, + replaces); + ReleaseSysCache(oldtup); + CatalogTupleUpdate(sd, &stup->t_self, stup); + } + else + { + /* No, insert new tuple */ + stup = heap_form_tuple(RelationGetDescr(sd), values, nulls); + CatalogTupleInsert(sd, stup); + } + + heap_freetuple(stup); + } } table_close(sd, RowExclusiveLock); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 61853e6dec4..e0b8d1d0db7 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -33,6 +33,7 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" +#include "catalog/storage_gtt.h" #include "catalog/toasting.h" #include "commands/cluster.h" #include "commands/defrem.h" @@ -390,6 +391,22 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params) errmsg("cannot vacuum temporary tables of other sessions"))); } + /* + * Skip the global temporary table that did not initialize the storage + * in this backend. + */ + if (RELATION_IS_GLOBAL_TEMP(OldHeap)) + { + if (gtt_storage_attached(RelationGetRelid(OldHeap))) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot cluster global temporary table"))); + + relation_close(OldHeap, AccessExclusiveLock); + pgstat_progress_end_command(); + return; + } + /* * Also check for active uses of the relation in the current transaction, * including open scans and pending AFTER trigger events. @@ -585,6 +602,9 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose) TransactionId frozenXid; MultiXactId cutoffMulti; + /* not support cluster global temp table yet */ + Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap)); + /* Mark the correct index as clustered */ if (OidIsValid(indexOid)) mark_index_clustered(OldHeap, indexOid, true); @@ -1429,7 +1449,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, PROGRESS_CLUSTER_PHASE_REBUILD_INDEX); - reindex_relation(OIDOldHeap, reindex_flags, &reindex_params); + reindex_relation(OIDOldHeap, reindex_flags, &reindex_params, ShareLock); /* Report that we are now doing clean up */ pgstat_progress_update_param(PROGRESS_CLUSTER_PHASE, diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index bb9c21bc6b4..18cd1d8c282 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -289,7 +289,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, Assert(rel); /* check read-only transaction and parallel mode */ - if (XactReadOnly && !rel->rd_islocaltemp) + if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel)) PreventCommandIfReadOnly("COPY FROM"); cstate = BeginCopyFrom(pstate, rel, whereClause, diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c index 7b3f5a84b82..03692b4a73a 100644 --- a/src/backend/commands/copyfrom.c +++ b/src/backend/commands/copyfrom.c @@ -30,6 +30,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/namespace.h" +#include "catalog/storage_gtt.h" #include "commands/copy.h" #include "commands/copyfrom_internal.h" #include "commands/progress.h" @@ -652,7 +653,7 @@ CopyFrom(CopyFromState cstate) */ ExecInitRangeTable(estate, cstate->range_table); resultRelInfo = target_resultRelInfo = makeNode(ResultRelInfo); - ExecInitResultRelation(estate, resultRelInfo, 1); + ExecInitResultRelation(estate, resultRelInfo, 1, CMD_INSERT); /* Verify the named relation is a valid target for INSERT */ CheckValidResultRel(resultRelInfo, CMD_INSERT); diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 9abbb6b5552..a944f244496 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -32,6 +32,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/namespace.h" +#include "catalog/storage_gtt.h" #include "catalog/toasting.h" #include "commands/createas.h" #include "commands/matview.h" @@ -520,6 +521,12 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) */ intoRelationDesc = table_open(intoRelationAddr.objectId, AccessExclusiveLock); + /* + * Try initializing the global Temp table storage file before writing data + * to the table. + */ + gtt_init_storage(CMD_INSERT, intoRelationDesc); + /* * Make sure the constructed table does not have RLS enabled. * diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index e5cf1bde13f..8daaed03bbc 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -111,6 +111,7 @@ struct ReindexIndexCallbackState { ReindexParams params; /* options from statement */ Oid locked_table_oid; /* tracks previously locked table */ + LOCKMODE lockmode; }; /* @@ -570,7 +571,7 @@ DefineIndex(Oid relationId, * is more efficient. Do this before any use of the concurrent option is * done. */ - if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP) + if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId))) concurrent = true; else concurrent = false; @@ -2581,24 +2582,46 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel) */ state.params = *params; state.locked_table_oid = InvalidOid; + state.lockmode = AccessShareLock; indOid = RangeVarGetRelidExtended(indexRelation, - (params->options & REINDEXOPT_CONCURRENTLY) != 0 ? - ShareUpdateExclusiveLock : AccessExclusiveLock, + AccessShareLock, 0, RangeVarCallbackForReindexIndex, &state); /* * Obtain the current persistence and kind of the existing index. We - * already hold a lock on the index. + * already hold a AccessShareLock on the index. + * If this is not a global temp object, apply a larger lock. */ persistence = get_rel_persistence(indOid); - relkind = get_rel_relkind(indOid); + if (persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + LOCKMODE table_lockmode; + LOCKMODE index_lockmode; + + if ((params->options & REINDEXOPT_CONCURRENTLY) != 0) + { + table_lockmode = ShareUpdateExclusiveLock; + index_lockmode = ShareUpdateExclusiveLock; + } + else + { + table_lockmode = ShareLock; + index_lockmode = AccessExclusiveLock; + } + /* lock heap first */ + Assert(OidIsValid(state.locked_table_oid)); + LockRelationOid(state.locked_table_oid, table_lockmode); + LockRelationOid(indOid, index_lockmode); + } + + relkind = get_rel_relkind(indOid); if (relkind == RELKIND_PARTITIONED_INDEX) ReindexPartitions(indOid, params, isTopLevel); else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - persistence != RELPERSISTENCE_TEMP) + !RelpersistenceTsTemp(persistence)) ReindexRelationConcurrently(indOid, params); else { @@ -2620,15 +2643,7 @@ RangeVarCallbackForReindexIndex(const RangeVar *relation, { char relkind; struct ReindexIndexCallbackState *state = arg; - LOCKMODE table_lockmode; - - /* - * Lock level here should match table lock in reindex_index() for - * non-concurrent case and table locks used by index_concurrently_*() for - * concurrent case. - */ - table_lockmode = (state->params.options & REINDEXOPT_CONCURRENTLY) != 0 ? - ShareUpdateExclusiveLock : ShareLock; + LOCKMODE table_lockmode = state->lockmode; /* * If we previously locked some other index's heap, and the name we're @@ -2689,6 +2704,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel) { Oid heapOid; bool result; + char relpersistence; + LOCKMODE lockmode; /* * The lock level used here should match reindex_relation(). @@ -2699,15 +2716,27 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel) * locks on our temporary table. */ heapOid = RangeVarGetRelidExtended(relation, - (params->options & REINDEXOPT_CONCURRENTLY) != 0 ? - ShareUpdateExclusiveLock : ShareLock, + AccessShareLock, 0, RangeVarCallbackOwnsTable, NULL); + relpersistence = get_rel_persistence(heapOid); + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + lockmode = AccessShareLock; + else + { + if ((params->options & REINDEXOPT_CONCURRENTLY) != 0) + lockmode = ShareUpdateExclusiveLock; + else + lockmode = ShareLock; + + LockRelationOid(heapOid, lockmode); + } + if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE) ReindexPartitions(heapOid, params, isTopLevel); else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP) + !RelpersistenceTsTemp(relpersistence)) { result = ReindexRelationConcurrently(heapOid, params); @@ -2724,7 +2753,8 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel) result = reindex_relation(heapOid, REINDEX_REL_PROCESS_TOAST | REINDEX_REL_CHECK_CONSTRAINTS, - &newparams); + &newparams, + lockmode); if (!result) ereport(NOTICE, (errmsg("table \"%s\" has no indexes to reindex", @@ -3119,7 +3149,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params) Assert(!RELKIND_HAS_PARTITIONS(relkind)); if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 && - relpersistence != RELPERSISTENCE_TEMP) + !RelpersistenceTsTemp(relpersistence)) { ReindexParams newparams = *params; @@ -3141,13 +3171,20 @@ ReindexMultipleInternal(List *relids, ReindexParams *params) { bool result; ReindexParams newparams = *params; + LOCKMODE lockmode; + + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + lockmode = AccessShareLock; + else + lockmode = ShareLock; newparams.options |= REINDEXOPT_REPORT_PROGRESS | REINDEXOPT_MISSING_OK; result = reindex_relation(relid, REINDEX_REL_PROCESS_TOAST | REINDEX_REL_CHECK_CONSTRAINTS, - &newparams); + &newparams, + lockmode); if (result && (params->options & REINDEXOPT_VERBOSE) != 0) ereport(INFO, diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 4b3f79704f8..c544885f53e 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -51,12 +51,33 @@ LockTableCommand(LockStmt *lockstmt) RangeVar *rv = (RangeVar *) lfirst(p); bool recurse = rv->inh; Oid reloid; + LOCKMODE lockmode = lockstmt->mode; + char relpersistence; - reloid = RangeVarGetRelidExtended(rv, lockstmt->mode, - lockstmt->nowait ? RVR_NOWAIT : 0, + reloid = RangeVarGetRelidExtended(rv, NoLock, 0, RangeVarCallbackForLockTable, (void *) &lockstmt->mode); + relpersistence = get_rel_persistence(reloid); + /* lock statement does not hold any lock on global temp table */ + if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP) + { + if (!lockstmt->nowait) + LockRelationOid(reloid, lockmode); + else if (!ConditionalLockRelationOid(reloid, lockmode)) + { + /* try to throw error by name; relation could be deleted... */ + char *relname = get_rel_name(reloid); + + if (!relname) + return; /* child concurrently dropped, just skip it */ + ereport(ERROR, + (errcode(ERRCODE_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%s\"", + relname))); + } + } + if (get_rel_relkind(reloid) == RELKIND_VIEW) LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL); else if (recurse) diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 27cb6307581..093f76f2950 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -25,11 +25,14 @@ #include "access/xloginsert.h" #include "access/xlogutils.h" #include "catalog/dependency.h" +#include "catalog/heap.h" #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_sequence.h" #include "catalog/pg_type.h" +#include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/defrem.h" #include "commands/sequence.h" #include "commands/tablecmds.h" @@ -108,6 +111,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity, List **owned_by); static void do_setval(Oid relid, int64 next, bool iscalled); static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity); +int64 get_seqence_start_value(Oid seqid); /* @@ -220,9 +224,16 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) rel = table_open(seqoid, AccessExclusiveLock); tupDesc = RelationGetDescr(rel); - /* now initialize the sequence's data */ - tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + /* + * For global temp sequence, the storage is not initialized + * when it is created, but when it is used. + */ + if (!RELATION_IS_GLOBAL_TEMP(rel)) + { + /* now initialize the sequence's data */ + tuple = heap_form_tuple(tupDesc, value, null); + fill_seq_with_data(rel, tuple); + } /* process OWNED BY if given */ if (owned_by) @@ -275,8 +286,6 @@ ResetSequence(Oid seq_relid) Buffer buf; HeapTupleData seqdatatuple; HeapTuple tuple; - HeapTuple pgstuple; - Form_pg_sequence pgsform; int64 startv; /* @@ -287,12 +296,7 @@ ResetSequence(Oid seq_relid) init_sequence(seq_relid, &elm, &seq_rel); (void) read_seq_tuple(seq_rel, &buf, &seqdatatuple); - 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); + startv = get_seqence_start_value(seq_relid); /* * Copy the existing sequence tuple. @@ -451,6 +455,9 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) init_sequence(relid, &elm, &seqrel); + if (RELATION_IS_GLOBAL_TEMP(seqrel)) + CheckGlobalTempTableNotInUse(seqrel, "ALTER GLOBAL TEMPORARY SEQUENCE"); + rel = table_open(SequenceRelationId, RowExclusiveLock); seqtuple = SearchSysCacheCopy1(SEQRELID, ObjectIdGetDatum(relid)); @@ -611,7 +618,7 @@ nextval_internal(Oid relid, bool check_permissions) RelationGetRelationName(seqrel)))); /* read-only transactions may only modify temp sequences */ - if (!seqrel->rd_islocaltemp) + if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel)) PreventCommandIfReadOnly("nextval()"); /* @@ -936,7 +943,7 @@ do_setval(Oid relid, int64 next, bool iscalled) ReleaseSysCache(pgstuple); /* read-only transactions may only modify temp sequences */ - if (!seqrel->rd_islocaltemp) + if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel)) PreventCommandIfReadOnly("setval()"); /* @@ -1153,6 +1160,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) /* Return results */ *p_elm = elm; *p_rel = seqrel; + + /* Initializes the storage for sequence which the global temporary table belongs. */ + if (RELATION_IS_GLOBAL_TEMP(seqrel) && + !gtt_storage_attached(RelationGetRelid(seqrel))) + { + RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel); + gtt_init_sequence(seqrel); + } } @@ -1927,3 +1942,57 @@ seq_mask(char *page, BlockNumber blkno) mask_unused_space(page); } + +/* + * Get the startValue of the sequence from syscache. + */ +int64 +get_seqence_start_value(Oid seqid) +{ + HeapTuple seqtuple; + Form_pg_sequence seqform; + int64 start; + + seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid)); + if (!HeapTupleIsValid(seqtuple)) + elog(ERROR, "cache lookup failed for sequence %u", + seqid); + + seqform = (Form_pg_sequence) GETSTRUCT(seqtuple); + start = seqform->seqstart; + ReleaseSysCache(seqtuple); + + return start; +} + +/* + * Initialize sequence which global temporary table belongs. + */ +void +gtt_init_sequence(Relation rel) +{ + Datum value[SEQ_COL_LASTCOL]; + bool null[SEQ_COL_LASTCOL]; + HeapTuple tuple; + int64 startv = get_seqence_start_value(RelationGetRelid(rel)); + + /* + * last_value from pg_sequence.seqstart + * log_cnt = 0 + * is_called = false + */ + value[SEQ_COL_LASTVAL - 1] = Int64GetDatumFast(startv); /* start sequence with 1 */ + null[SEQ_COL_LASTVAL - 1] = false; + + value[SEQ_COL_LOG - 1] = Int64GetDatum((int64)0); + null[SEQ_COL_LOG - 1] = false; + + value[SEQ_COL_CALLED - 1] = BoolGetDatum(false); + null[SEQ_COL_CALLED - 1] = false; + + tuple = heap_form_tuple(RelationGetDescr(rel), value, null); + fill_seq_with_data(rel, tuple); + heap_freetuple(tuple); + + return; +} diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1f0654c2f51..a558e121596 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -47,6 +47,7 @@ #include "catalog/storage.h" #include "catalog/storage_xlog.h" #include "catalog/toasting.h" +#include "catalog/storage_gtt.h" #include "commands/cluster.h" #include "commands/comment.h" #include "commands/defrem.h" @@ -118,6 +119,7 @@ typedef struct OnCommitItem */ SubTransactionId creating_subid; SubTransactionId deleting_subid; + bool is_global_temp; } OnCommitItem; static List *on_commits = NIL; @@ -624,7 +626,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); static char GetAttributeCompression(Oid atttypid, char *compression); - +static OnCommitAction gtt_oncommit_option(List *options); /* ---------------------------------------------------------------- * DefineRelation @@ -669,6 +671,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, LOCKMODE parentLockmode; const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; + OnCommitAction oncommit_action = ONCOMMIT_NOOP; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -680,7 +683,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * Check consistency of arguments */ if (stmt->oncommit != ONCOMMIT_NOOP - && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) + && !RelpersistenceTsTemp(stmt->relation->relpersistence)) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); @@ -710,7 +713,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, * code. This is needed because calling code might not expect untrusted * tables to appear in pg_temp at the front of its search path. */ - if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP + if (RelpersistenceTsTemp(stmt->relation->relpersistence) && InSecurityRestrictedOperation()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -811,6 +814,59 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ + /* For global temporary table */ + oncommit_action = gtt_oncommit_option(stmt->options); + if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + if (!(relkind == RELKIND_RELATION || relkind == RELKIND_SEQUENCE)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("global temporary relation can only be a regular table or sequence"))); + + if (inheritOids) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot create global temporary inherit table or global temporary partitioned table"))); + + /* Check oncommit clause and save to reloptions */ + if (oncommit_action != ONCOMMIT_NOOP) + { + if (stmt->oncommit != ONCOMMIT_NOOP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot specify both ON COMMIT clause and on_commit_delete_rows"))); + + stmt->oncommit = oncommit_action; + } + else + { + DefElem *opt = makeNode(DefElem); + + opt->type = T_DefElem; + opt->defnamespace = NULL; + opt->defname = "on_commit_delete_rows"; + opt->defaction = DEFELEM_UNSPEC; + + /* use reloptions to remember on commit clause */ + if (stmt->oncommit == ONCOMMIT_DELETE_ROWS) + opt->arg = (Node *)makeString("true"); + else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS) + opt->arg = (Node *)makeString("false"); + else if (stmt->oncommit == ONCOMMIT_NOOP) + opt->arg = (Node *)makeString("false"); + else if (stmt->oncommit == ONCOMMIT_DROP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("specifying ON COMMIT DROP is not supported on a global temporary table"))); + + stmt->options = lappend(stmt->options, opt); + } + } + else if (oncommit_action != ONCOMMIT_NOOP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("on_commit_delete_rows can only be used on global temporary table"))); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, true, false); @@ -1435,7 +1491,7 @@ RemoveRelations(DropStmt *drop) * relation persistence cannot be known without its OID. */ if (drop->concurrent && - get_rel_persistence(relOid) != RELPERSISTENCE_TEMP) + !RelpersistenceTsTemp(get_rel_persistence(relOid))) { Assert(list_length(drop->objects) == 1 && drop->removeType == OBJECT_INDEX); @@ -1644,9 +1700,9 @@ ExecuteTruncate(TruncateStmt *stmt) Relation rel; bool recurse = rv->inh; Oid myrelid; - LOCKMODE lockmode = AccessExclusiveLock; + LOCKMODE lockmode; - myrelid = RangeVarGetRelidExtended(rv, lockmode, + myrelid = RangeVarGetRelidExtended(rv, AccessShareLock, 0, RangeVarCallbackForTruncate, NULL); @@ -1654,9 +1710,21 @@ ExecuteTruncate(TruncateStmt *stmt) if (list_member_oid(relids, myrelid)) continue; - /* open the relation, we already hold a lock on it */ + /* open the relation, we need hold a low-level lock first */ rel = table_open(myrelid, NoLock); + /* + * Truncate global temp table only cleans up the data in current session, + * only low-level locks are required. + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + lockmode = AccessShareLock; + else + { + lockmode = AccessExclusiveLock; + LockRelationOid(myrelid, lockmode); + } + /* * RangeVarGetRelidExtended() has done most checks with its callback, * but other checks with the now-opened Relation remain. @@ -1906,6 +1974,7 @@ ExecuteTruncateGuts(List *explicit_rels, foreach(cell, rels) { Relation rel = (Relation) lfirst(cell); + LOCKMODE lockmode; /* Skip partitioned tables as there is nothing to do */ if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) @@ -1956,6 +2025,19 @@ ExecuteTruncateGuts(List *explicit_rels, continue; } + /* + * Skip the global temporary table that is not initialized for storage + * in current session. + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + lockmode = AccessShareLock; + if (!gtt_storage_attached(RelationGetRelid(rel))) + continue; + } + else + lockmode = AccessExclusiveLock; + /* * Normally, we need a transaction-safe truncation here. However, if * the table was either created in the current (sub)transaction or has @@ -1967,7 +2049,7 @@ ExecuteTruncateGuts(List *explicit_rels, rel->rd_newRelfilenodeSubid == mySubid) { /* Immediate, non-rollbackable truncation is OK */ - heap_truncate_one_rel(rel); + heap_truncate_one_rel(rel, lockmode); } else { @@ -2001,7 +2083,7 @@ ExecuteTruncateGuts(List *explicit_rels, if (OidIsValid(toast_relid)) { Relation toastrel = relation_open(toast_relid, - AccessExclusiveLock); + lockmode); RelationSetNewRelfilenode(toastrel, toastrel->rd_rel->relpersistence); @@ -2012,7 +2094,7 @@ ExecuteTruncateGuts(List *explicit_rels, * Reconstruct the indexes to match, and we're done. */ reindex_relation(heap_relid, REINDEX_REL_PROCESS_TOAST, - &reindex_params); + &reindex_params, lockmode); } pgstat_count_truncate(rel); @@ -3283,6 +3365,12 @@ CheckRelationTableSpaceMove(Relation rel, Oid newTableSpaceId) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot move temporary tables of other sessions"))); + if (RELATION_IS_GLOBAL_TEMP(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot move global temporary table \"%s\"", + RelationGetRelationName(rel)))); + return true; } @@ -4052,6 +4140,10 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, /* Caller is required to provide an adequate lock. */ rel = relation_open(context->relid, NoLock); + /* We allow to alter global temporary table only current session use it */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + CheckGlobalTempTableNotInUse(rel, "ALTER GLOBAL TEMPORARY TABLE"); + CheckTableNotInUse(rel, "ALTER TABLE"); ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); @@ -5383,6 +5475,25 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, rel = table_open(tab->relid, NoLock); find_composite_type_dependencies(rel->rd_rel->reltype, rel, NULL); + + if (RELATION_IS_GLOBAL_TEMP(rel) && tab->rewrite > 0) + { + if (tab->chgPersistence) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change global temporary table persistence setting"))); + + if(gtt_storage_attached(tab->relid)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot rewrite global temporary table \"%s\" when it has data in this session", + RelationGetRelationName(rel)), + errhint("Please create a new connection and execute ALTER TABLE on the new connection."))); + + /* global temp table has no data in this session, so only change catalog */ + tab->rewrite = 0; + } + table_close(rel, NoLock); } @@ -5434,6 +5545,9 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rewrite temporary tables of other sessions"))); + /* Not support rewrite global temp table */ + Assert(!RELATION_IS_GLOBAL_TEMP(OldHeap)); + /* * Select destination tablespace (same as original unless user * requested a change) @@ -9081,6 +9195,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("constraints on temporary tables must involve temporary tables of this session"))); break; + case RELPERSISTENCE_GLOBAL_TEMP: + if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("constraints on global temporary tables may reference only global temporary tables"))); + break; } /* @@ -13439,7 +13559,9 @@ TryReuseIndex(Oid oldId, IndexStmt *stmt) Relation irel = index_open(oldId, NoLock); /* If it's a partitioned index, there is no storage to share. */ - if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX) + /* multiple global temp table are not allow use same relfilenode */ + if (irel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX && + !RELATION_IS_GLOBAL_TEMP(irel)) { stmt->oldNode = irel->rd_node.relNode; stmt->oldCreateSubid = irel->rd_createSubid; @@ -14101,6 +14223,11 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ + /* option on_commit_delete_rows is only for global temp table and cannot be set by ALTER TABLE */ + if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP) + elog(ERROR, "cannot set \"on_commit_delete_rows\" for relation \"%s\"", + RelationGetRelationName(rel)); + pgclass = table_open(RelationRelationId, RowExclusiveLock); /* Fetch heap tuple */ @@ -14601,7 +14728,7 @@ index_copy_data(Relation rel, RelFileNode newrnode) * NOTE: any conflict in relfilenode value will be caught in * RelationCreateStorage(). */ - RelationCreateStorage(newrnode, rel->rd_rel->relpersistence); + RelationCreateStorage(newrnode, rel->rd_rel->relpersistence, rel); /* copy main fork */ RelationCopyStorage(RelationGetSmgr(rel), dstrel, MAIN_FORKNUM, @@ -16202,6 +16329,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged) switch (rel->rd_rel->relpersistence) { case RELPERSISTENCE_TEMP: + case RELPERSISTENCE_GLOBAL_TEMP: ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot change logged status of table \"%s\" because it is temporary", @@ -16643,7 +16771,7 @@ AlterSeqNamespaces(Relation classRel, Relation rel, * Register a newly-created relation's ON COMMIT action. */ void -register_on_commit_action(Oid relid, OnCommitAction action) +register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp) { OnCommitItem *oc; MemoryContext oldcxt; @@ -16662,6 +16790,7 @@ register_on_commit_action(Oid relid, OnCommitAction action) oc->oncommit = action; oc->creating_subid = GetCurrentSubTransactionId(); oc->deleting_subid = InvalidSubTransactionId; + oc->is_global_temp = is_gloal_temp; /* * We use lcons() here so that ON COMMIT actions are processed in reverse @@ -16707,6 +16836,7 @@ PreCommit_on_commit_actions(void) ListCell *l; List *oids_to_truncate = NIL; List *oids_to_drop = NIL; + List *oids_to_truncate_gtt = NIL; foreach(l, on_commits) { @@ -16730,7 +16860,12 @@ PreCommit_on_commit_actions(void) * tables, as they must still be empty. */ if ((MyXactFlags & XACT_FLAGS_ACCESSEDTEMPNAMESPACE)) - oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid); + { + if (oc->is_global_temp) + oids_to_truncate_gtt = lappend_oid(oids_to_truncate_gtt, oc->relid); + else + oids_to_truncate = lappend_oid(oids_to_truncate, oc->relid); + } break; case ONCOMMIT_DROP: oids_to_drop = lappend_oid(oids_to_drop, oc->relid); @@ -16747,7 +16882,10 @@ PreCommit_on_commit_actions(void) * exists at truncation time. */ if (oids_to_truncate != NIL) - heap_truncate(oids_to_truncate); + heap_truncate(oids_to_truncate, false); + + if (oids_to_truncate_gtt != NIL) + heap_truncate(oids_to_truncate_gtt, true); if (oids_to_drop != NIL) { @@ -17746,6 +17884,13 @@ ATExecAttachPartition(List **wqueue, Relation rel, PartitionCmd *cmd, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot attach temporary relation of another session as partition"))); + /* If the parent is permanent, so must be all of its partitions. */ + if (attachrel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot attach a global temporary relation as partition of permanent relation \"%s\"", + RelationGetRelationName(rel)))); + /* Check if there are any columns in attachrel that aren't in the parent */ tupleDesc = RelationGetDescr(attachrel); natts = tupleDesc->natts; @@ -19238,3 +19383,39 @@ GetAttributeCompression(Oid atttypid, char *compression) return cmethod; } + +/* + * Parse the on commit clause for the temporary table + */ +static OnCommitAction +gtt_oncommit_option(List *options) +{ + ListCell *listptr; + OnCommitAction action = ONCOMMIT_NOOP; + + foreach(listptr, options) + { + DefElem *def = (DefElem *) lfirst(listptr); + + if (strcmp(def->defname, "on_commit_delete_rows") == 0) + { + bool res = false; + char *sval = defGetString(def); + + /* It has to be a Boolean value */ + if (!parse_bool(sval, &res)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value"))); + + if (res) + action = ONCOMMIT_DELETE_ROWS; + else + action = ONCOMMIT_PRESERVE_ROWS; + + break; + } + } + + return action; +} diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 283ffaea77d..a1bee3393a0 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -39,7 +39,9 @@ #include "catalog/pg_database.h" #include "catalog/pg_inherits.h" #include "catalog/pg_namespace.h" +#include "catalog/storage_gtt.h" #include "commands/cluster.h" +#include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -1323,6 +1325,11 @@ vac_update_relstats(Relation relation, Form_pg_class pgcform; bool dirty; + if (RELATION_IS_GLOBAL_TEMP(relation)) + return vac_update_gtt_relstats(relation, num_pages, num_tuples, + num_all_visible_pages, hasindex, + frozenxid, minmulti, in_outer_xact); + rd = table_open(RelationRelationId, RowExclusiveLock); /* Fetch a copy of the tuple to scribble on */ @@ -1417,7 +1424,6 @@ vac_update_relstats(Relation relation, table_close(rd, RowExclusiveLock); } - /* * vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB * @@ -1509,6 +1515,13 @@ vac_update_datfrozenxid(void) continue; } + /* + * The relfrozenxid for a global temporary talble is stored in localhash, + * not pg_class, See list_all_session_gtt_frozenxids() + */ + if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + continue; + /* * Some table AMs might not need per-relation xid / multixid horizons. * It therefore seems reasonable to allow relfrozenxid and relminmxid @@ -1566,6 +1579,42 @@ vac_update_datfrozenxid(void) Assert(TransactionIdIsNormal(newFrozenXid)); Assert(MultiXactIdIsValid(newMinMulti)); + /* If enable global temporary table */ + if (max_active_gtt > 0) + { + TransactionId safe_age; + TransactionId oldest_gtt_frozenxid = + gtt_get_oldest_frozenxids_in_current_database(NULL, NULL); + + if (TransactionIdIsNormal(oldest_gtt_frozenxid)) + { + safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age; + if (safe_age < FirstNormalTransactionId) + safe_age += FirstNormalTransactionId; + + /* + * We tolerate that the minimum age of gtt is less than + * the minimum age of conventional tables, otherwise it will + * throw warning message. + */ + if (TransactionIdIsNormal(safe_age) && + TransactionIdPrecedes(safe_age, newFrozenXid)) + { + ereport(WARNING, + (errmsg("global temporary table oldest relfrozenxid %u is far in the past", + oldest_gtt_frozenxid), + errdetail("The oldest relfrozenxid %u in database \"%s\"", newFrozenXid, get_database_name(MyDatabaseId)), + errhint("please consider cleaning up the data in global temporary table to avoid wraparound problems."))); + } + + /* + * We need to ensure that the clog required by gtt is not cleand. + */ + if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid)) + newFrozenXid = oldest_gtt_frozenxid; + } + } + /* Now fetch the pg_database tuple we need to update. */ relation = table_open(DatabaseRelationId, RowExclusiveLock); @@ -1917,6 +1966,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) return false; } + /* + * Skip those global temporary table that are not initialized in + * this session. + */ + if (RELATION_IS_GLOBAL_TEMP(rel) && + !gtt_storage_attached(RelationGetRelid(rel))) + { + relation_close(rel, lmode); + PopActiveSnapshot(); + CommitTransactionCommand(); + return false; + } + /* * Silently ignore tables that are temp tables of other backends --- * trying to vacuum these will lead to great unhappiness, since their diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index e183ab097c4..04ae8ff7073 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -527,6 +527,12 @@ DefineView(ViewStmt *stmt, const char *queryString, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("views cannot be unlogged because they do not have storage"))); + /* Global temporary table are not sensible. */ + if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("views cannot be global temporary because they do not have storage"))); + /* * If the user didn't explicitly ask for a temporary view, check whether * we need one implicitly. We allow TEMP to be inserted automatically as diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 549d9eb6963..370e4d09d1d 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -784,6 +784,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) if (isTempNamespace(get_rel_namespace(rte->relid))) continue; + /* Global temp table is one kind of temp table */ + if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP) + continue; + PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt)); } diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 9df1f81ea89..2207dccf274 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -50,6 +50,7 @@ #include "access/table.h" #include "access/tableam.h" #include "access/transam.h" +#include "catalog/storage_gtt.h" #include "executor/executor.h" #include "executor/execPartition.h" #include "jit/jit.h" @@ -832,7 +833,7 @@ ExecGetRangeTableRelation(EState *estate, Index rti) */ void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, - Index rti) + Index rti, CmdType operation) { Relation resultRelationDesc; @@ -843,6 +844,9 @@ ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, NULL, estate->es_instrument); + /* Check and init global temporary table storage in this session */ + gtt_init_storage(operation, resultRelationDesc); + if (estate->es_result_relations == NULL) estate->es_result_relations = (ResultRelInfo **) palloc0(estate->es_range_table_size * sizeof(ResultRelInfo *)); diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 5ec699a9bd1..1401d999b40 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -38,6 +38,7 @@ #include "access/tableam.h" #include "access/xact.h" #include "catalog/catalog.h" +#include "catalog/storage_gtt.h" #include "commands/trigger.h" #include "executor/execPartition.h" #include "executor/executor.h" @@ -2754,13 +2755,13 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) { mtstate->rootResultRelInfo = makeNode(ResultRelInfo); ExecInitResultRelation(estate, mtstate->rootResultRelInfo, - node->rootRelation); + node->rootRelation, operation); } else { mtstate->rootResultRelInfo = mtstate->resultRelInfo; ExecInitResultRelation(estate, mtstate->resultRelInfo, - linitial_int(node->resultRelations)); + linitial_int(node->resultRelations), operation); } /* set up epqstate with dummy subplan data for the moment */ @@ -2788,7 +2789,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) if (resultRelInfo != mtstate->rootResultRelInfo) { - ExecInitResultRelation(estate, resultRelInfo, resultRelation); + ExecInitResultRelation(estate, resultRelInfo, resultRelation, operation); /* * For child result relations, store the root result relation diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 169b1d53fc8..07a616d626b 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -48,7 +48,7 @@ #include "partitioning/partprune.h" #include "rewrite/rewriteManip.h" #include "utils/lsyscache.h" - +#include "utils/rel.h" /* results of subquery_is_pushdown_safe */ typedef struct pushdown_safety_info @@ -619,7 +619,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, * the rest of the necessary infrastructure right now anyway. So * for now, bail out if we see a temporary table. */ - if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP) + if (RelpersistenceTsTemp(get_rel_persistence(rte->relid))) return; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index bd09f85aea1..a56f0b8ceee 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6071,7 +6071,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * Furthermore, any index predicate or index expressions must be parallel * safe. */ - if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + if (RELATION_IS_TEMP(heap) || !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index a5002ad8955..22f64506caa 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -31,6 +31,7 @@ #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" #include "catalog/pg_statistic_ext_data.h" +#include "catalog/storage_gtt.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -222,6 +223,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, continue; } + /* Ignore empty index for global temporary table in this session */ + if (RELATION_IS_GLOBAL_TEMP(indexRelation) && + !gtt_storage_attached(RelationGetRelid(indexRelation))) + { + index_close(indexRelation, NoLock); + continue; + } + /* * If the index is valid, but cannot yet be used, ignore it; but * mark the plan we are generating as transient. See diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index 6ac2e9ce237..2d62d0a4036 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2902,7 +2902,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) * creation query. It would be hard to refresh data or incrementally * maintain it if a source disappeared. */ - if (isQueryUsingTempRelation(query)) + if (isQueryUsingTempRelation(query) || isQueryUsingGlobalTempRelation(query)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialized views must not use temporary tables or views"))); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index b5966712ce1..388303a2d85 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3434,17 +3434,11 @@ OptTemp: TEMPORARY { $$ = RELPERSISTENCE_TEMP; } | LOCAL TEMP { $$ = RELPERSISTENCE_TEMP; } | GLOBAL TEMPORARY { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; + $$ = RELPERSISTENCE_GLOBAL_TEMP; } | GLOBAL TEMP { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); - $$ = RELPERSISTENCE_TEMP; + $$ = RELPERSISTENCE_GLOBAL_TEMP; } | UNLOGGED { $$ = RELPERSISTENCE_UNLOGGED; } | /*EMPTY*/ { $$ = RELPERSISTENCE_PERMANENT; } @@ -11843,19 +11837,13 @@ OptTempTableName: } | GLOBAL TEMPORARY opt_table qualified_name { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); $$ = $4; - $$->relpersistence = RELPERSISTENCE_TEMP; + $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP; } | GLOBAL TEMP opt_table qualified_name { - ereport(WARNING, - (errmsg("GLOBAL is deprecated in temporary table creation"), - parser_errposition(@1))); $$ = $4; - $$->relpersistence = RELPERSISTENCE_TEMP; + $$->relpersistence = RELPERSISTENCE_GLOBAL_TEMP; } | UNLOGGED opt_table qualified_name { diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c index cb9e177b5e5..e333f427740 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -82,6 +82,7 @@ static void expandTupleDesc(TupleDesc tupdesc, Alias *eref, List **colnames, List **colvars); static int specialAttNum(const char *attname); static bool isQueryUsingTempRelation_walker(Node *node, void *context); +static bool isQueryUsingGlobalTempRelation_walker(Node *node, void *context); /* @@ -3665,3 +3666,52 @@ isQueryUsingTempRelation_walker(Node *node, void *context) isQueryUsingTempRelation_walker, context); } + +/* + * Like function isQueryUsingTempRelation_walker + * return true if any relation underlying + * the query is a global temporary table. + */ +static bool +isQueryUsingGlobalTempRelation_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Query)) + { + Query *query = (Query *) node; + ListCell *rtable; + + foreach(rtable, query->rtable) + { + RangeTblEntry *rte = lfirst(rtable); + + if (rte->rtekind == RTE_RELATION) + { + Relation rel = table_open(rte->relid, AccessShareLock); + char relpersistence = rel->rd_rel->relpersistence; + + table_close(rel, AccessShareLock); + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return true; + } + } + + return query_tree_walker(query, + isQueryUsingGlobalTempRelation_walker, + context, + QTW_IGNORE_JOINALIASES); + } + + return expression_tree_walker(node, + isQueryUsingGlobalTempRelation_walker, + context); +} + +/* Check if the query uses global temporary table */ +bool +isQueryUsingGlobalTempRelation(Query *query) +{ + return isQueryUsingGlobalTempRelation_walker((Node *) query, NULL); +} diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 0eea214dd89..00233b13658 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -447,6 +447,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = seqoptions; + /* + * If a sequence is bound to a global temporary table, then the sequence + * must been "global temporary" + */ + if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + seqstmt->sequence->relpersistence = cxt->relation->relpersistence; + /* * If a sequence data type was specified, add it to the options. Prepend * to the list rather than append; in case a user supplied their own AS @@ -3326,6 +3333,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.isforeign = false; } cxt.relation = stmt->relation; + /* Set the relpersistence to the context */ + cxt.relation->relpersistence = RelationGetRelPersistence(rel); cxt.rel = rel; cxt.inhRelations = NIL; cxt.isalter = true; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 681ef91b81e..4ea4c5bfcc7 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2116,6 +2116,14 @@ do_autovacuum(void) } continue; } + else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* + * Aotuvacuum cannot vacuum the private data stored in each session + * that belongs to global temporary table, so skip them. + */ + continue; + } /* Fetch reloptions and the pgstat entry for this table */ relopts = extract_autovac_opts(tuple, pg_class_desc); @@ -2182,7 +2190,7 @@ do_autovacuum(void) /* * We cannot safely process other backends' temp tables, so skip 'em. */ - if (classForm->relpersistence == RELPERSISTENCE_TEMP) + if (RelpersistenceTsTemp(classForm->relpersistence)) continue; relid = classForm->oid; diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index a2512e750c2..9aefdf48cb9 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -37,6 +37,7 @@ #include "access/xlogutils.h" #include "catalog/catalog.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "executor/instrument.h" #include "lib/binaryheap.h" #include "miscadmin.h" @@ -2934,7 +2935,15 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln) BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum) { - if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) + /* + * Returns 0 if this global temporary table is not initialized in this session. + */ + if (RELATION_IS_GLOBAL_TEMP(relation) && + !gtt_storage_attached(RelationGetRelid(relation))) + { + return 0; + } + else if (RELKIND_HAS_TABLE_AM(relation->rd_rel->relkind)) { /* * Not every table AM uses BLCKSZ wide fixed size blocks. diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 9f26e41c464..0a25b151e82 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -22,6 +22,7 @@ #include "access/subtrans.h" #include "access/syncscan.h" #include "access/twophase.h" +#include "catalog/storage_gtt.h" #include "commands/async.h" #include "miscadmin.h" #include "pgstat.h" @@ -143,6 +144,7 @@ CalculateShmemSize(int *num_semaphores) size = add_size(size, BTreeShmemSize()); size = add_size(size, SyncScanShmemSize()); size = add_size(size, AsyncShmemSize()); + size = add_size(size, active_gtt_shared_hash_size()); #ifdef EXEC_BACKEND size = add_size(size, ShmemBackendArraySize()); #endif @@ -246,6 +248,8 @@ CreateSharedMemoryAndSemaphores(void) SUBTRANSShmemInit(); MultiXactShmemInit(); InitBufferPool(); + /* For global temporary table shared hashtable */ + active_gtt_shared_hash_init(); /* * Set up lock manager diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 3be60402890..dfb78547c0a 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -65,6 +65,7 @@ #include "utils/builtins.h" #include "utils/rel.h" #include "utils/snapmgr.h" +#include "utils/guc.h" #define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var)))) @@ -5161,3 +5162,66 @@ KnownAssignedXidsReset(void) LWLockRelease(ProcArrayLock); } + +/* + * Search all active session to get db level oldest frozenxid + * for global temporary table. + * + * Pids and Xids are used to store the session level oldest frozenxid if specified + */ +TransactionId +gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids) +{ + ProcArrayStruct *arrayP = NULL; + TransactionId result = InvalidTransactionId; + int index = 0; + int i = 0; + uint8 flags = 0; + + /* return 0 if feature is disabled */ + if (max_active_gtt <= 0) + return InvalidTransactionId; + + /* Disable in standby node */ + if (RecoveryInProgress()) + return InvalidTransactionId; + + flags |= PROC_IS_AUTOVACUUM; + flags |= PROC_IN_LOGICAL_DECODING; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + arrayP = procArray; + for (index = 0; index < arrayP->numProcs; index++) + { + int pgprocno = arrayP->pgprocnos[index]; + PGPROC *proc = &allProcs[pgprocno]; + uint8 statusFlags = ProcGlobal->statusFlags[index]; + TransactionId gtt_frozenxid = InvalidTransactionId; + + if (statusFlags & flags) + continue; + + /* Fetch all session level frozenxid that is belonging to current database */ + gtt_frozenxid = proc->gtt_frozenxid; + if (proc->databaseId == MyDatabaseId && + TransactionIdIsNormal(gtt_frozenxid)) + { + if (result == InvalidTransactionId) + result = gtt_frozenxid; + else if (TransactionIdPrecedes(gtt_frozenxid, result)) + result = gtt_frozenxid; + + /* save backend pid and session level oldest relfrozenxid */ + if (pids) + *pids = lappend_int(*pids, proc->pid); + + if (xids) + *xids = lappend_oid(*xids, gtt_frozenxid); + + i++; + } + } + LWLockRelease(ProcArrayLock); + + return result; +} diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c index 7b0dea4abec..ab0d2031e86 100644 --- a/src/backend/storage/lmgr/lwlock.c +++ b/src/backend/storage/lmgr/lwlock.c @@ -176,7 +176,9 @@ static const char *const BuiltinTrancheNames[] = { /* LWTRANCHE_PARALLEL_APPEND: */ "ParallelAppend", /* LWTRANCHE_PER_XACT_PREDICATE_LIST: */ - "PerXactPredicateList" + "PerXactPredicateList", + /* LWTRANCHE_GLOBAL_TEMP_TABLE_CTL */ + "GlobalTempTableControl" }; StaticAssertDecl(lengthof(BuiltinTrancheNames) == diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index e306b04738d..c9e90714c3b 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -391,6 +391,7 @@ InitProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyProc->delayChkpt = false; MyProc->statusFlags = 0; @@ -576,6 +577,7 @@ InitAuxiliaryProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyProc->delayChkpt = false; MyProc->statusFlags = 0; diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 3a2f2e1f99d..9a6e7cee244 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS) Assert(backend != InvalidBackendId); } break; + case RELPERSISTENCE_GLOBAL_TEMP: + /* + * For global temporary table ,each backend has its own storage, + * also only sees its own storage. Use Backendid to identify them. + */ + backend = BackendIdForTempRelations(); + break; default: elog(ERROR, "invalid relpersistence: %c", relform->relpersistence); backend = InvalidBackendId; /* placate compiler */ diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c index 1fbb0b28c3b..637ff7f7e43 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -108,6 +108,7 @@ #include "catalog/pg_operator.h" #include "catalog/pg_statistic.h" #include "catalog/pg_statistic_ext.h" +#include "catalog/storage_gtt.h" #include "executor/nodeAgg.h" #include "miscadmin.h" #include "nodes/makefuncs.h" @@ -5117,12 +5118,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid, } else if (index->indpred == NIL) { - vardata->statsTuple = - SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(index->indexoid), - Int16GetDatum(pos + 1), - BoolGetDatum(false)); - vardata->freefunc = ReleaseSysCache; + char rel_persistence = get_rel_persistence(index->indexoid); + + if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata->statsTuple = + get_gtt_att_statistic(index->indexoid, + Int16GetDatum(pos + 1), + false); + vardata->freefunc = release_gtt_statistic_cache; + } + else + { + vardata->statsTuple = + SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(index->indexoid), + Int16GetDatum(pos + 1), + BoolGetDatum(false)); + vardata->freefunc = ReleaseSysCache; + } if (HeapTupleIsValid(vardata->statsTuple)) { @@ -5365,15 +5380,28 @@ examine_simple_variable(PlannerInfo *root, Var *var, } else if (rte->rtekind == RTE_RELATION) { - /* - * Plain table or parent of an inheritance appendrel, so look up the - * column in pg_statistic - */ - vardata->statsTuple = SearchSysCache3(STATRELATTINH, - ObjectIdGetDatum(rte->relid), - Int16GetDatum(var->varattno), - BoolGetDatum(rte->inh)); - vardata->freefunc = ReleaseSysCache; + char rel_persistence = get_rel_persistence(rte->relid); + + if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata->statsTuple = get_gtt_att_statistic(rte->relid, + var->varattno, + rte->inh); + vardata->freefunc = release_gtt_statistic_cache; + } + else + { + /* + * Plain table or parent of an inheritance appendrel, so look up the + * column in pg_statistic + */ + vardata->statsTuple = SearchSysCache3(STATRELATTINH, + ObjectIdGetDatum(rte->relid), + Int16GetDatum(var->varattno), + BoolGetDatum(rte->inh)); + vardata->freefunc = ReleaseSysCache; + } if (HeapTupleIsValid(vardata->statsTuple)) { @@ -6817,6 +6845,7 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, { /* Simple variable --- look to stats for the underlying table */ RangeTblEntry *rte = planner_rt_fetch(index->rel->relid, root); + char rel_persistence = get_rel_persistence(rte->relid); Assert(rte->rtekind == RTE_RELATION); relid = rte->relid; @@ -6834,6 +6863,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + rte->inh); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -6845,6 +6882,8 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, } else { + char rel_persistence = get_rel_persistence(index->indexoid); + /* Expression --- maybe there are stats for the index itself */ relid = index->indexoid; colnum = 1; @@ -6860,6 +6899,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -7778,6 +7825,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, /* attempt to lookup stats in relation for this index column */ if (attnum != 0) { + char rel_persistence = get_rel_persistence(rte->relid); + /* Simple variable -- look to stats for the underlying table */ if (get_relation_stats_hook && (*get_relation_stats_hook) (root, rte, attnum, &vardata)) @@ -7790,6 +7839,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata.statsTuple = + get_gtt_att_statistic(rte->relid, + attnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = @@ -7802,6 +7860,8 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, } else { + char rel_persistence = get_rel_persistence(index->indexoid); + /* * Looks like we've found an expression column in the index. Let's * see if there's any stats for it. @@ -7821,6 +7881,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count, !vardata.freefunc) elog(ERROR, "no function provided to release variable stats with"); } + else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + vardata.statsTuple = + get_gtt_att_statistic(index->indexoid, + attnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index feef9998634..ebaad33a698 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -35,6 +35,7 @@ #include "catalog/pg_statistic.h" #include "catalog/pg_transform.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "utils/array.h" @@ -3113,6 +3114,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum) if (stawidth > 0) return stawidth; } + if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP) + { + /* For global temporary table, get statistic data from localhash */ + tp = get_gtt_att_statistic(relid, attnum, false); + if (!HeapTupleIsValid(tp)) + return 0; + + stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth; + if (stawidth > 0) + return stawidth; + else + return 0; + } tp = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(relid), Int16GetDatum(attnum), diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 2e760e8a3bd..758a635b75e 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -65,6 +65,7 @@ #include "catalog/pg_type.h" #include "catalog/schemapg.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/policy.h" #include "commands/trigger.h" #include "miscadmin.h" @@ -1152,6 +1153,36 @@ retry: relation->rd_islocaltemp = false; } break; + case RELPERSISTENCE_GLOBAL_TEMP: + { + BlockNumber relpages = 0; + double reltuples = 0; + BlockNumber relallvisible = 0; + TransactionId relfrozenxid = InvalidTransactionId; + MultiXactId relminmxid = InvalidMultiXactId; + + relation->rd_backend = BackendIdForTempRelations(); + relation->rd_islocaltemp = false; + + /* + * For global temporary table + * get session level relstats from localhash + * and set it to local relcache + */ + get_gtt_relstats(RelationGetRelid(relation), + &relpages, &reltuples, &relallvisible, + &relfrozenxid, &relminmxid); + + relation->rd_rel->relpages = (int32)relpages; + relation->rd_rel->reltuples = (float4)reltuples; + relation->rd_rel->relallvisible = (int32)relallvisible; + if (TransactionIdIsNormal(relfrozenxid)) + relation->rd_rel->relfrozenxid = relfrozenxid; + + if (MultiXactIdIsValid(relminmxid)) + relation->rd_rel->relminmxid = relminmxid; + } + break; default: elog(ERROR, "invalid relpersistence: %c", relation->rd_rel->relpersistence); @@ -1212,6 +1243,15 @@ retry: else Assert(relation->rd_rel->relam == InvalidOid); + /* + * For one global temporary table, + * other session may created one index, that triggers relcache reload for this session. + * If table already has data at this session, to avoid rebuilding index, + * accept the structure of the index but set the local state to indisvalid. + */ + if (relation->rd_rel->relkind == RELKIND_INDEX) + gtt_correct_index_session_state(relation); + /* extract reloptions if any */ RelationParseRelOptions(relation, pg_class_tuple); @@ -1335,7 +1375,23 @@ RelationInitPhysicalAddr(Relation relation) heap_freetuple(phys_tuple); } - relation->rd_node.relNode = relation->rd_rel->relfilenode; + /* + * For global temporary table, + * the latest relfilenode is saved in localHash(see RelationSetNewRelfilenode()), + * get it and put it to local relcache. + */ + if (RELATION_IS_GLOBAL_TEMP(relation)) + { + Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation)); + + if (OidIsValid(newrelnode) && + newrelnode != relation->rd_rel->relfilenode) + relation->rd_node.relNode = newrelnode; + else + relation->rd_node.relNode = relation->rd_rel->relfilenode; + } + else + relation->rd_node.relNode = relation->rd_rel->relfilenode; } else { @@ -2288,6 +2344,14 @@ RelationReloadIndexInfo(Relation relation) HeapTupleHeaderGetXmin(tuple->t_data)); ReleaseSysCache(tuple); + + /* + * For one global temporary table, + * other session may created one index, that triggers relcache reload for this session. + * If table already has data at this session, to avoid rebuilding index, + * accept the structure of the index but set the local state to indisvalid. + */ + gtt_correct_index_session_state(relation); } /* Okay, now it's valid again */ @@ -3568,6 +3632,10 @@ RelationBuildLocalRelation(const char *relname, rel->rd_backend = BackendIdForTempRelations(); rel->rd_islocaltemp = true; break; + case RELPERSISTENCE_GLOBAL_TEMP: + rel->rd_backend = BackendIdForTempRelations(); + rel->rd_islocaltemp = false; + break; default: elog(ERROR, "invalid relpersistence: %c", relpersistence); break; @@ -3681,6 +3749,13 @@ RelationSetNewRelfilenode(Relation relation, char persistence) TransactionId freezeXid = InvalidTransactionId; RelFileNode newrnode; + /* + * For global temporary table, storage information for each session is + * maintained locally, not in pg_class. + */ + if (RELATION_IS_GLOBAL_TEMP(relation)) + return GlobalTempRelationSetNewRelfilenode(relation); + /* Allocate a new relfilenode */ newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL, persistence); @@ -3724,7 +3799,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence) /* handle these directly, at least for now */ SMgrRelation srel; - srel = RelationCreateStorage(newrnode, persistence); + srel = RelationCreateStorage(newrnode, persistence, relation); smgrclose(srel); } else diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 4c94f09c645..b4017794a35 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -44,6 +44,7 @@ #include "catalog/namespace.h" #include "catalog/pg_authid.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/async.h" #include "commands/prepare.h" #include "commands/tablespace.h" @@ -154,6 +155,18 @@ char *GUC_check_errmsg_string; char *GUC_check_errdetail_string; char *GUC_check_errhint_string; +/* + * num = 0 means disable global temporary table feature. + * table schema are still saved in catalog. + * + * num > 0 means allows the database to manage multiple active tables at the same time. + */ +#define MIN_NUM_ACTIVE_GTT 0 +#define DEFAULT_NUM_ACTIVE_GTT 1000 +#define MAX_NUM_ACTIVE_GTT 1000000 + +int max_active_gtt = MIN_NUM_ACTIVE_GTT; + static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4); static void set_config_sourcefile(const char *name, char *sourcefile, @@ -2141,6 +2154,15 @@ static struct config_bool ConfigureNamesBool[] = static struct config_int ConfigureNamesInt[] = { + { + {"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED, + gettext_noop("max active global temporary table."), + NULL + }, + &max_active_gtt, + DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT, + NULL, NULL, NULL + }, { {"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING, gettext_noop("Sets the amount of time to wait before forcing a " @@ -2712,6 +2734,16 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("The defer check age of GTT, used to check expired data after vacuum."), + NULL + }, + &vacuum_gtt_defer_check_age, + 10000, 0, 1000000, + NULL, NULL, NULL + }, + /* * See also CheckRequiredParameterValues() if this parameter changes */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7c2f1d30447..cb18546ad74 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2538,6 +2538,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) dopt->no_unlogged_table_data) return; + /* Don't dump data in global temporary table/sequence */ + if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return; + /* Check that the data is not explicitly excluded */ if (simple_oid_list_member(&tabledata_exclude_oids, tbinfo->dobj.catId.oid)) @@ -14913,6 +14917,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) char *ftoptions = NULL; char *srvname = NULL; char *foreign = ""; + char *table_type = NULL; /* * Set reltypename, and collect any relkind-specific data that we @@ -14988,9 +14993,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) binary_upgrade_set_pg_class_oids(fout, q, tbinfo->dobj.catId.oid, false); + if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED) + table_type = "UNLOGGED "; + else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + table_type = "GLOBAL TEMPORARY "; + else + table_type = ""; + appendPQExpBuffer(q, "CREATE %s%s %s", - tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ? - "UNLOGGED " : "", + table_type, reltypename, qualrelname); @@ -15358,6 +15369,15 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) } } + /* + * Transaction information for the global temporary table is not stored + * in the pg_class. + */ + if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + Assert(tbinfo->frozenxid == 0); + Assert(tbinfo->minmxid == 0); + } /* * In binary_upgrade mode, arrange to restore the old relfrozenxid and * relminmxid of all vacuumable relations. (While vacuum.c processes @@ -15365,7 +15385,7 @@ dumpTableSchema(Archive *fout, const TableInfo *tbinfo) * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the * child toast table is handled below.) */ - if (dopt->binary_upgrade && + else if (dopt->binary_upgrade && (tbinfo->relkind == RELKIND_RELATION || tbinfo->relkind == RELKIND_MATVIEW)) { @@ -16385,6 +16405,7 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) PQExpBuffer query = createPQExpBuffer(); PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; + bool global_temp_seq = false; qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); @@ -16394,9 +16415,12 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) "SELECT format_type(seqtypid, NULL), " "seqstart, seqincrement, " "seqmax, seqmin, " - "seqcache, seqcycle " - "FROM pg_catalog.pg_sequence " - "WHERE seqrelid = '%u'::oid", + "seqcache, seqcycle, " + "c.relpersistence " + "FROM pg_catalog.pg_sequence s, " + "pg_catalog.pg_class c " + "WHERE seqrelid = '%u'::oid " + "and s.seqrelid = c.oid", tbinfo->dobj.catId.oid); } else @@ -16433,6 +16457,9 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + if (fout->remoteVersion >= 150000) + global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0); + /* Calculate default limits for a sequence of this type */ is_ascending = (incby[0] != '-'); if (strcmp(seqtype, "smallint") == 0) @@ -16510,9 +16537,13 @@ dumpSequence(Archive *fout, const TableInfo *tbinfo) } else { - appendPQExpBuffer(query, - "CREATE SEQUENCE %s\n", - fmtQualifiedDumpable(tbinfo)); + appendPQExpBuffer(query, "CREATE "); + + if (global_temp_seq) + appendPQExpBuffer(query, "GLOBAL TEMP "); + + appendPQExpBuffer(query, "SEQUENCE %s\n", + fmtQualifiedDumpable(tbinfo)); if (strcmp(seqtype, "bigint") != 0) appendPQExpBuffer(query, " AS %s\n", seqtype); diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c index 3d218c2ad24..b120ff8e71d 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -88,7 +88,7 @@ check_and_dump_old_cluster(bool live_check) start_postmaster(&old_cluster, true); /* Extract a list of databases and tables from the old cluster */ - get_db_and_rel_infos(&old_cluster); + get_db_and_rel_infos(&old_cluster, true); init_tablespaces(); @@ -174,7 +174,7 @@ check_and_dump_old_cluster(bool live_check) void check_new_cluster(void) { - get_db_and_rel_infos(&new_cluster); + get_db_and_rel_infos(&new_cluster, false); check_new_cluster_is_empty(); check_databases_are_compatible(); diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c index f7fa0820d69..028e0700e37 100644 --- a/src/bin/pg_upgrade/info.c +++ b/src/bin/pg_upgrade/info.c @@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db); static void free_db_and_rel_infos(DbInfoArr *db_arr); static void get_db_infos(ClusterInfo *cluster); -static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo); +static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp); static void free_rel_infos(RelInfoArr *rel_arr); static void print_db_infos(DbInfoArr *dbinfo); static void print_rel_infos(RelInfoArr *rel_arr); @@ -271,9 +271,11 @@ report_unmatched_relation(const RelInfo *rel, const DbInfo *db, bool is_new_db) * * higher level routine to generate dbinfos for the database running * on the given "port". Assumes that server is already running. + * for check object need check global temp table, + * for create object skip global temp table. */ void -get_db_and_rel_infos(ClusterInfo *cluster) +get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp) { int dbnum; @@ -283,7 +285,7 @@ get_db_and_rel_infos(ClusterInfo *cluster) get_db_infos(cluster); for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++) - get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]); + get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_global_temp); if (cluster == &old_cluster) pg_log(PG_VERBOSE, "\nsource databases:\n"); @@ -368,7 +370,7 @@ get_db_infos(ClusterInfo *cluster) * This allows later processing to match up old and new databases efficiently. */ static void -get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo) +get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_global_temp) { PGconn *conn = connectToServer(cluster, dbinfo->db_name); @@ -411,8 +413,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo) " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n " " ON c.relnamespace = n.oid " " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", " - CppAsString2(RELKIND_MATVIEW) ") AND " + CppAsString2(RELKIND_MATVIEW) ") AND "); + + if (skip_global_temp) + { + /* exclude global temp tables */ + snprintf(query + strlen(query), sizeof(query) - strlen(query), + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "); + } + /* exclude possible orphaned temp tables */ + snprintf(query + strlen(query), sizeof(query) - strlen(query), " ((n.nspname !~ '^pg_temp_' AND " " n.nspname !~ '^pg_toast_temp_' AND " " n.nspname NOT IN ('pg_catalog', 'information_schema', " diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index 07defacd673..438ae344d09 100644 --- a/src/bin/pg_upgrade/pg_upgrade.c +++ b/src/bin/pg_upgrade/pg_upgrade.c @@ -411,7 +411,7 @@ create_new_objects(void) set_frozenxids(true); /* update new_cluster info now that we have objects in the databases */ - get_db_and_rel_infos(&new_cluster); + get_db_and_rel_infos(&new_cluster, true); } /* @@ -649,7 +649,10 @@ set_frozenxids(bool minmxid_only) "UPDATE pg_catalog.pg_class " "SET relfrozenxid = '%u' " /* only heap, materialized view, and TOAST are vacuumed */ - "WHERE relkind IN (" + "WHERE " + /* exclude global temp tables */ + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + "relkind IN (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_TOASTVALUE) ")", @@ -660,7 +663,10 @@ set_frozenxids(bool minmxid_only) "UPDATE pg_catalog.pg_class " "SET relminmxid = '%u' " /* only heap, materialized view, and TOAST are vacuumed */ - "WHERE relkind IN (" + "WHERE " + /* exclude global temp tables */ + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + "relkind IN (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_TOASTVALUE) ")", diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h index da6770d0f83..2da01c28f37 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -372,7 +372,7 @@ void check_loadable_libraries(void); FileNameMap *gen_db_file_maps(DbInfo *old_db, DbInfo *new_db, int *nmaps, const char *old_pgdata, const char *new_pgdata); -void get_db_and_rel_infos(ClusterInfo *cluster); +void get_db_and_rel_infos(ClusterInfo *cluster, bool skip_global_temp); /* option.c */ diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 40433e32fa0..d1bbab6029b 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3769,7 +3769,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys * Show whether a relation is permanent, temporary, or unlogged. */ appendPQExpBuffer(&buf, - ",\n CASE c.relpersistence WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"", + ",\n CASE c.relpersistence WHEN 'g' THEN '%s' WHEN 'p' THEN '%s' WHEN 't' THEN '%s' WHEN 'u' THEN '%s' END as \"%s\"", + gettext_noop("session"), gettext_noop("permanent"), gettext_noop("temporary"), gettext_noop("unlogged"), diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 6bd33a06cb1..d65daa3855b 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1103,6 +1103,8 @@ static const pgsql_thing_t words_after_create[] = { {"FOREIGN TABLE", NULL, NULL, NULL}, {"FUNCTION", NULL, NULL, Query_for_list_of_functions}, {"GROUP", Query_for_list_of_roles}, + {"GLOBAL", NULL, NULL, NULL, THING_NO_DROP | THING_NO_ALTER}, /* for CREATE GLOBAL TEMP/TEMPORARY TABLE + * ... */ {"INDEX", NULL, NULL, &Query_for_list_of_indexes}, {"LANGUAGE", Query_for_list_of_languages}, {"LARGE OBJECT", NULL, NULL, NULL, THING_NO_CREATE | THING_NO_DROP}, @@ -2670,6 +2672,9 @@ psql_completion(const char *text, int start, int end) /* CREATE FOREIGN DATA WRAPPER */ else if (Matches("CREATE", "FOREIGN", "DATA", "WRAPPER", MatchAny)) COMPLETE_WITH("HANDLER", "VALIDATOR", "OPTIONS"); + /* CREATE GLOBAL TEMP/TEMPORARY*/ + else if (Matches("CREATE", "GLOBAL")) + COMPLETE_WITH("TEMP", "TEMPORARY"); /* CREATE FOREIGN TABLE */ else if (Matches("CREATE", "FOREIGN", "TABLE", MatchAny)) @@ -2904,6 +2909,8 @@ psql_completion(const char *text, int start, int end) /* Complete "CREATE TEMP/TEMPORARY" with the possible temp objects */ else if (TailMatches("CREATE", "TEMP|TEMPORARY")) COMPLETE_WITH("SEQUENCE", "TABLE", "VIEW"); + else if (TailMatches("CREATE", "GLOBAL", "TEMP|TEMPORARY")) + COMPLETE_WITH("TABLE", "SEQUENCE"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) COMPLETE_WITH("TABLE", "MATERIALIZED VIEW"); diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index c4757bda2d5..d99454c984f 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -86,9 +86,9 @@ extern Oid heap_create_with_catalog(const char *relname, extern void heap_drop_with_catalog(Oid relid); -extern void heap_truncate(List *relids); +extern void heap_truncate(List *relids, bool is_global_temp); -extern void heap_truncate_one_rel(Relation rel); +extern void heap_truncate_one_rel(Relation rel, LOCKMODE lockmode); extern void heap_truncate_check_FKs(List *relations, bool tempTables); @@ -161,5 +161,5 @@ extern void StorePartitionKey(Relation rel, extern void RemovePartitionKeyByRelId(Oid relid); extern void StorePartitionBound(Relation rel, Relation parent, PartitionBoundSpec *bound); - +extern void CheckGlobalTempTableNotInUse(Relation rel, const char *stmt); #endif /* HEAP_H */ diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h index a1d6e3b645f..441cd440493 100644 --- a/src/include/catalog/index.h +++ b/src/include/catalog/index.h @@ -158,7 +158,7 @@ extern void reindex_index(Oid indexId, bool skip_constraint_checks, #define REINDEX_REL_FORCE_INDEXES_UNLOGGED 0x08 #define REINDEX_REL_FORCE_INDEXES_PERMANENT 0x10 -extern bool reindex_relation(Oid relid, int flags, ReindexParams *params); +extern bool reindex_relation(Oid relid, int flags, ReindexParams *params, LOCKMODE lockmode); extern bool ReindexIsProcessingHeap(Oid heapOid); extern bool ReindexIsProcessingIndex(Oid indexOid); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 304e8c18d52..e23cb49c010 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -172,6 +172,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, ClassTblspcRelfilenodeInd #define RELPERSISTENCE_PERMANENT 'p' /* regular table */ #define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */ #define RELPERSISTENCE_TEMP 't' /* temporary table */ +#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */ /* default selection for replica identity (primary key or nothing) */ #define REPLICA_IDENTITY_DEFAULT 'd' diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 0859dc81cac..60d92e2096f 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5729,6 +5729,40 @@ proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_xact_function_self_time' }, +# For global temporary table +{ oid => '9874', + descr => 'List local statistics for global temporary table', + proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement', + proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}', + proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}', + proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}', + prosrc => 'pg_get_gtt_statistics' }, +{ oid => '9875', + descr => 'List local relstats for global temporary table', + proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid', + proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}', + proargmodes => '{i,o,o,o,o,o,o}', + proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}', + prosrc => 'pg_get_gtt_relstats' }, +{ oid => '9876', + descr => 'List attached pid for one global temporary table', + proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid', + proallargtypes => '{oid,oid,int4}', + proargmodes => '{i,o,o}', + proargnames => '{relid,relid,pid}', + prosrc => 'pg_gtt_attached_pid' }, +{ oid => '9877', + descr => 'List those backends that have used global temporary table', + proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u', + prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '', + proallargtypes => '{int4,xid}', + proargmodes => '{o,o}', + proargnames => '{pid,relfrozenxid}', + prosrc => 'pg_list_gtt_relfrozenxids' }, + { oid => '3788', descr => 'statistics: timestamp of the current statistics snapshot', proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's', diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h index 9ffc7419131..ca5bcaa3f97 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -22,7 +22,7 @@ /* GUC variables */ extern int wal_skip_threshold; -extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence); +extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel); extern void RelationDropStorage(Relation rel); extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit); extern void RelationPreTruncate(Relation rel); diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h new file mode 100644 index 00000000000..50e0570945a --- /dev/null +++ b/src/include/catalog/storage_gtt.h @@ -0,0 +1,46 @@ +/*------------------------------------------------------------------------- + * + * storage_gtt.h + * prototypes for functions in backend/catalog/storage_gtt.c + * + * src/include/catalog/storage_gtt.h + * + *------------------------------------------------------------------------- + */ +#ifndef STORAGE_GTT_H +#define STORAGE_GTT_H + +#include "access/htup.h" +#include "storage/block.h" +#include "storage/relfilenode.h" +#include "nodes/execnodes.h" +#include "utils/relcache.h" + +extern int vacuum_gtt_defer_check_age; + +extern Size active_gtt_shared_hash_size(void); +extern void active_gtt_shared_hash_init(void); +extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel); +extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit); +extern bool is_other_backend_use_gtt(Oid relid); +extern bool gtt_storage_attached(Oid relid); +extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts, + TupleDesc tupleDescriptor, Datum *values, bool *isnull); +extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh); +extern void release_gtt_statistic_cache(HeapTuple tup); +extern void gtt_update_relstats(Relation relation, BlockNumber relpages, double reltuples, + BlockNumber relallvisible, TransactionId relfrozenxid, + TransactionId relminmxid); +extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples, + BlockNumber *relallvisible, TransactionId *relfrozenxid, + TransactionId *relminmxid); +extern void force_enable_gtt_index(Relation index); +extern void gtt_correct_index_session_state(Relation index); +extern void gtt_init_storage(CmdType operation, Relation relation); +extern Oid gtt_fetch_current_relfilenode(Oid relid); +extern void index_update_gtt_relstats(Relation rel, bool hasindex, double reltuples, bool isreindex); +extern void vac_update_gtt_relstats(Relation relation, BlockNumber num_pages, double num_tuples, + BlockNumber num_all_visible_pages, bool hasindex, TransactionId frozenxid, + MultiXactId minmulti, bool in_outer_xact); +extern void GlobalTempRelationSetNewRelfilenode(Relation relation); +#endif /* STORAGE_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index f7542f2bccb..e8ae0f00531 100644 --- a/src/include/commands/sequence.h +++ b/src/include/commands/sequence.h @@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr); extern void seq_desc(StringInfo buf, XLogReaderState *rptr); extern const char *seq_identify(uint8 info); extern void seq_mask(char *pagedata, BlockNumber blkno); +extern void gtt_init_sequence(Relation rel); #endif /* SEQUENCE_H */ diff --git a/src/include/commands/tablecmds.h b/src/include/commands/tablecmds.h index 5d4037f26e8..28598b06c1e 100644 --- a/src/include/commands/tablecmds.h +++ b/src/include/commands/tablecmds.h @@ -86,7 +86,7 @@ extern void find_composite_type_dependencies(Oid typeOid, extern void check_of_type(HeapTuple typetuple); -extern void register_on_commit_action(Oid relid, OnCommitAction action); +extern void register_on_commit_action(Oid relid, OnCommitAction action, bool is_gloal_temp); extern void remove_on_commit_action(Oid relid); extern void PreCommit_on_commit_actions(void); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 344399f6a8a..ae7628d692b 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -575,7 +575,7 @@ exec_rt_fetch(Index rti, EState *estate) extern Relation ExecGetRangeTableRelation(EState *estate, Index rti); extern void ExecInitResultRelation(EState *estate, ResultRelInfo *resultRelInfo, - Index rti); + Index rti, CmdType operation); extern int executor_errposition(EState *estate, int location); diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 06dc27995ba..197e367f4e4 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -119,5 +119,6 @@ extern const NameData *attnumAttName(Relation rd, int attid); extern Oid attnumTypeId(Relation rd, int attid); extern Oid attnumCollationId(Relation rd, int attid); extern bool isQueryUsingTempRelation(Query *query); +extern bool isQueryUsingGlobalTempRelation(Query *query); #endif /* PARSE_RELATION_H */ diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index 124977cf7e3..9b64389b08a 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -189,6 +189,7 @@ typedef enum BuiltinTrancheIds LWTRANCHE_SHARED_TIDBITMAP, LWTRANCHE_PARALLEL_APPEND, LWTRANCHE_PER_XACT_PREDICATE_LIST, + LWTRANCHE_GLOBAL_TEMP_TABLE_CTL, LWTRANCHE_FIRST_USER_DEFINED } BuiltinTrancheIds; diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index a58888f9e90..1bbf31923c0 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -164,6 +164,8 @@ struct PGPROC Oid tempNamespaceId; /* OID of temp schema this backend is * using */ + TransactionId gtt_frozenxid; /* session level global temp table relfrozenxid */ + bool isBackgroundWorker; /* true if background worker. */ /* diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h index e03692053ee..d948c0383b7 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin, extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin, TransactionId *catalog_xmin); +extern TransactionId gtt_get_oldest_frozenxids_in_current_database(List **pids, List **xids); + #endif /* PROCARRAY_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 6bb81707b09..4927edc2dec 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -288,6 +288,9 @@ extern int tcp_user_timeout; extern bool trace_sort; #endif +/* global temporary table */ +extern int max_active_gtt; + /* * Functions exported by guc.c */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 6da1b220cdc..6455dfb7ae2 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -57,7 +57,7 @@ typedef struct RelationData SMgrRelation rd_smgr; /* cached file handle, or NULL */ int rd_refcnt; /* reference count */ BackendId rd_backend; /* owning backend id, if temporary relation */ - bool rd_islocaltemp; /* rel is a temp rel of this session */ + bool rd_islocaltemp; /* rel is a temp rel of this session */ bool rd_isnailed; /* rel is nailed in cache */ bool rd_isvalid; /* relcache entry is valid */ bool rd_indexvalid; /* is rd_indexlist valid? (also rd_pkindex and @@ -327,6 +327,7 @@ typedef struct StdRdOptions int parallel_workers; /* max number of parallel workers */ StdRdOptIndexCleanup vacuum_index_cleanup; /* controls index vacuuming */ bool vacuum_truncate; /* enables vacuum to truncate a relation */ + bool on_commit_delete_rows; /* global temp table */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -609,11 +610,13 @@ RelationGetSmgr(Relation rel) * True if relation's pages are stored in local buffers. */ #define RelationUsesLocalBuffers(relation) \ - ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) /* * RELATION_IS_LOCAL - * If a rel is either temp or newly created in the current transaction, + * If a rel is either local temp or global temp relation + * or newly created in the current transaction, * it can be assumed to be accessible only to the current backend. * This is typically used to decide that we can skip acquiring locks. * @@ -621,6 +624,7 @@ RelationGetSmgr(Relation rel) */ #define RELATION_IS_LOCAL(relation) \ ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \ (relation)->rd_createSubid != InvalidSubTransactionId) /* @@ -633,6 +637,30 @@ RelationGetSmgr(Relation rel) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ !(relation)->rd_islocaltemp) +/* + * RELATION_IS_TEMP_ON_CURRENT_SESSION + * Test a rel is either local temp relation of this session + * or global temp relation. + */ +#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \ + ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + +/* + * RELATION_IS_TEMP + * Test a rel is local temporary relation or global temporary relation. + */ +#define RELATION_IS_TEMP(relation) \ + ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + +/* + * RelpersistenceTsTemp + * Test a relpersistence is local temp relation or global temporary relation. + */ +#define RelpersistenceTsTemp(relpersistence) \ + (relpersistence == RELPERSISTENCE_TEMP || \ + relpersistence == RELPERSISTENCE_GLOBAL_TEMP) /* * RelationIsScannable @@ -678,6 +706,19 @@ RelationGetSmgr(Relation rel) (relation)->rd_rel->relkind != RELKIND_FOREIGN_TABLE && \ !IsCatalogRelation(relation)) +/* For global temporary table */ +#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + +/* Get on commit clause value only for global temporary table */ +#define RELATION_GTT_ON_COMMIT_DELETE(relation) \ + ((relation)->rd_options && \ + ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \ + ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false) + +/* Get relpersistence for relation */ +#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence) + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); -- 2.32.0 (Apple Git-132)