diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 8ccc228..b061de9 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] = }, true }, + /* + * For global temp table only + * use ShareUpdateExclusiveLock for ensure safety + */ + { + { + "on_commit_delete_rows", + "global temp table on commit options", + RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED, + ShareUpdateExclusiveLock + }, + true + }, /* list terminator */ {{NULL}} }; @@ -1817,6 +1830,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)}, @@ -1961,13 +1976,18 @@ build_local_reloptions(local_relopts *relopts, Datum options, bool validate) bytea * partitioned_table_reloptions(Datum reloptions, bool validate) { + static const relopt_parse_elt tab[] = { + {"on_commit_delete_rows", RELOPT_TYPE_BOOL, + offsetof(StdRdOptions, on_commit_delete_rows)} + }; + /* * There are no options for partitioned tables yet, but this is able to do * some validation. */ return (bytea *) build_reloptions(reloptions, validate, RELOPT_KIND_PARTITIONED, - 0, NULL, 0); + sizeof(StdRdOptions), tab, lengthof(tab)); } /* diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c index 765329b..4761fdc 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -1011,7 +1011,9 @@ gistproperty(Oid index_oid, int attno, XLogRecPtr gistGetFakeLSN(Relation rel) { - if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP) + /* global temp is same as local temp table */ + if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { /* * Temporary relations are only accessible in our session, so a simple diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 3ec6d52..1222594 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -150,7 +150,9 @@ 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) + /* global temp table is same as local temp table */ + if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) sort_threshold = Min(sort_threshold, NBuffers); else sort_threshold = Min(sort_threshold, NLocBuffer); diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 56b3562..d3443ec 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -587,7 +587,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 @@ -640,7 +640,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(rel->rd_smgr, dstrel, MAIN_FORKNUM, diff --git a/src/backend/access/heap/vacuumlazy.c b/src/backend/access/heap/vacuumlazy.c index f3382d3..7a0746a 100644 --- a/src/backend/access/heap/vacuumlazy.c +++ b/src/backend/access/heap/vacuumlazy.c @@ -62,6 +62,7 @@ #include "access/xact.h" #include "access/xlog.h" #include "catalog/storage.h" +#include "catalog/storage_gtt.h" #include "commands/dbcommands.h" #include "commands/progress.h" #include "commands/vacuum.h" @@ -431,9 +432,9 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params, Assert(params->index_cleanup != VACOPT_TERNARY_DEFAULT); Assert(params->truncate != VACOPT_TERNARY_DEFAULT); - /* not every AM requires these to be valid, but heap does */ - Assert(TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); - Assert(MultiXactIdIsValid(onerel->rd_rel->relminmxid)); + /* not every AM requires these to be valid, but regular heap does */ + Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)); + Assert(RELATION_IS_GLOBAL_TEMP(onerel) ^ MultiXactIdIsValid(onerel->rd_rel->relminmxid)); /* measure elapsed time iff autovacuum logging requires it */ if (IsAutoVacuumWorkerProcess() && params->log_min_duration >= 0) diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index 39b8f17..abb76dc 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" @@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel) { Buffer metabuf; + 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/access/transam/xlog.c b/src/backend/access/transam/xlog.c index abf954b..a956fab 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -6395,6 +6395,10 @@ StartupXLOG(void) else recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID; + /* clean temp relation files */ + if (max_active_gtt > 0) + RemovePgTempFiles(); + /* * Check for signal files, and if so set up state for offline recovery */ diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 9499bb3..ae47364 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -43,6 +43,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 7d6acae..3be8d63 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -393,7 +393,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence) switch (relpersistence) { + /* global temp table is same as local temp table */ 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 632c058..01cf990 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -61,6 +61,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" @@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -427,7 +429,7 @@ heap_create(const char *relname, case RELKIND_INDEX: case RELKIND_SEQUENCE: - RelationCreateStorage(rel->rd_node, relpersistence); + RelationCreateStorage(rel->rd_node, relpersistence, rel); break; case RELKIND_RELATION: @@ -959,6 +961,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -997,8 +1000,18 @@ AddNewRelationTuple(Relation pg_class_desc, break; } - new_rel_reltup->relfrozenxid = relfrozenxid; - new_rel_reltup->relminmxid = relminmxid; + /* global temp table not remember transaction info in catalog */ + 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; @@ -1360,6 +1373,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relpersistence, relfrozenxid, relminmxid, PointerGetDatum(relacl), @@ -1940,6 +1954,14 @@ heap_drop_with_catalog(Oid relid) if (relid == defaultPartOid) update_default_partition_oid(parentOid, InvalidOid); + /* We allow to drop global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(rel))) + elog(ERROR, "can not drop relation %s when other backend attached this global temp table", + RelationGetRelationName(rel)); + } + /* * Schedule unlinking of the relation's physical files at commit. */ @@ -3167,7 +3189,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; @@ -3179,7 +3201,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 @@ -3225,8 +3247,13 @@ heap_truncate(List *relids) { Oid rid = lfirst_oid(cell); Relation rel; + LOCKMODE lockmode = AccessExclusiveLock; - rel = table_open(rid, AccessExclusiveLock); + /* truncate global temp table only need RowExclusiveLock */ + if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP) + lockmode = RowExclusiveLock; + + rel = table_open(rid, lockmode); relations = lappend(relations, rel); } @@ -3259,6 +3286,7 @@ void heap_truncate_one_rel(Relation rel) { Oid toastrelid; + LOCKMODE lockmode = AccessExclusiveLock; /* * Truncate the relation. Partitioned tables have no storage, so there is @@ -3267,23 +3295,37 @@ heap_truncate_one_rel(Relation rel) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) return; + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (!gtt_storage_attached(RelationGetRelid(rel))) + return; + + /* + * Truncate global temp table only need RowExclusiveLock + */ + lockmode = RowExclusiveLock; + } + /* Truncate the underlying relation */ 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); } + + if (RELATION_IS_GLOBAL_TEMP(rel)) + up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId); } /* diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index bd7ec92..afbb655 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -53,6 +53,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" @@ -881,6 +882,19 @@ index_create(Relation heapRelation, indexRelationName, RelationGetRelationName(heapRelation)))); } + if (RELATION_IS_GLOBAL_TEMP(heapRelation)) + { + /* No support create index on global temp table use concurrent mode yet */ + if (concurrent) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex global temporary tables concurrently"))); + + /* if global temp table not init storage, then skip build index */ + if (!gtt_storage_attached(RelationGetRelid(heapRelation))) + flags |= INDEX_CREATE_SKIP_BUILD; + } + /* * construct tuple descriptor for index tuples */ @@ -2034,6 +2048,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) indexrelid; LOCKTAG heaplocktag; LOCKMODE lockmode; + char rel_persistence; /* * A temporary relation uses a non-concurrent DROP. Other backends can't @@ -2041,7 +2056,8 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) * lock (see comments in RemoveRelations), and a non-concurrent DROP is * more efficient. */ - Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP || + rel_persistence = get_rel_persistence(indexId); + Assert(!(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP) || (!concurrent && !concurrent_lock_mode)); /* @@ -2073,6 +2089,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode) */ CheckTableNotInUse(userIndexRelation, "DROP INDEX"); + /* We allow to drop index on global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(userHeapRelation)) + { + if (is_other_backend_use_gtt(RelationGetRelid(userHeapRelation))) + elog(ERROR, "can not drop index %s when other backend attached this global temp table.", + RelationGetRelationName(userHeapRelation)); + } + /* * Drop Index Concurrently is more or less the reverse process of Create * Index Concurrently. @@ -2681,6 +2705,11 @@ index_update_stats(Relation rel, HeapTuple tuple; Form_pg_class rd_rel; bool dirty; + bool is_gtt = false; + + /* update index stats into localhash and rel_rd_rel for global temp table */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + is_gtt = true; /* * We always update the pg_class row using a non-transactional, @@ -2766,21 +2795,35 @@ index_update_stats(Relation rel, else /* don't bother for indexes */ relallvisible = 0; - if (rd_rel->relpages != (int32) relpages) + if (is_gtt) + rel->rd_rel->relpages = (int32) relpages; + else if (rd_rel->relpages != (int32) relpages) { rd_rel->relpages = (int32) relpages; dirty = true; } - if (rd_rel->reltuples != (float4) reltuples) + + if (is_gtt) + rel->rd_rel->reltuples = (float4) reltuples; + else if (rd_rel->reltuples != (float4) reltuples) { rd_rel->reltuples = (float4) reltuples; dirty = true; } - if (rd_rel->relallvisible != (int32) relallvisible) + + if (is_gtt) + rel->rd_rel->relallvisible = (int32) relallvisible; + else if (rd_rel->relallvisible != (int32) relallvisible) { rd_rel->relallvisible = (int32) relallvisible; dirty = true; } + + if (is_gtt) + { + up_gtt_relstats(rel, relpages, reltuples, relallvisible, + InvalidTransactionId, InvalidMultiXactId); + } } /* @@ -2894,6 +2937,15 @@ index_build(Relation heapRelation, pgstat_progress_update_multi_param(6, index, val); } + if (RELATION_IS_GLOBAL_TEMP(indexRelation)) + { + if (!gtt_storage_attached(RelationGetRelid(indexRelation))) + { + gtt_force_enable_index(indexRelation); + RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation); + } + } + /* * Call the access method's build procedure */ @@ -3442,6 +3494,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, PGRUsage ru0; bool progress = (options & REINDEXOPT_REPORT_PROGRESS) != 0; + if (persistence == RELPERSISTENCE_GLOBAL_TEMP && + !gtt_storage_attached(indexId)) + return; + pg_rusage_init(&ru0); /* diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 2ec2301..1b6061d 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid) errmsg("cannot create temporary relation in non-temporary schema"))); } break; + /* global temp table */ + case RELPERSISTENCE_GLOBAL_TEMP: + if (isAnyTempNamespace(nspid)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("cannot create global temp relations in temporary schemas"))); + 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 d713d5c..afcf004 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -27,6 +27,7 @@ #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" @@ -61,6 +62,7 @@ typedef struct PendingRelDelete { RelFileNode relnode; /* relation that may need to be deleted */ BackendId backend; /* InvalidBackendId if not a temp rel */ + Oid relOid; /* InvalidOid if not a global temp 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 +117,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; @@ -127,6 +129,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) switch (relpersistence) { case RELPERSISTENCE_TEMP: + /* global temp table use same storage strategy as local temp table */ + case RELPERSISTENCE_GLOBAL_TEMP: backend = BackendIdForTempRelations(); needs_wal = false; break; @@ -154,6 +158,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rnode; pending->backend = backend; + pending->relOid = InvalidOid; pending->atCommit = false; /* delete if abort */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; @@ -165,6 +170,13 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) AddPendingSync(&rnode); } + /* remember global temp table storage info to localhash */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel) + { + pending->relOid = RelationGetRelid(rel); + remember_gtt_storage_info(rnode, rel); + } + return srel; } @@ -201,11 +213,15 @@ RelationDropStorage(Relation rel) MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete)); pending->relnode = rel->rd_node; pending->backend = rel->rd_backend; + pending->relOid = InvalidOid; pending->atCommit = true; /* delete if commit */ pending->nestLevel = GetCurrentTransactionNestLevel(); pending->next = pendingDeletes; pendingDeletes = pending; + if (RELATION_IS_GLOBAL_TEMP(rel)) + pending->relOid = 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 @@ -602,6 +618,7 @@ smgrDoPendingDeletes(bool isCommit) i = 0, maxrels = 0; SMgrRelation *srels = NULL; + Oid *reloids = NULL; prev = NULL; for (pending = pendingDeletes; pending != NULL; pending = next) @@ -631,14 +648,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->relOid; + nrels++; } /* must explicitly free the list entry */ pfree(pending); @@ -651,9 +672,18 @@ smgrDoPendingDeletes(bool isCommit) smgrdounlinkall(srels, nrels, false); for (i = 0; i < nrels; i++) + { smgrclose(srels[i]); + /* clean global temp table flags when transaction commit or rollback */ + if (SmgrIsTemp(srels[i]) && + reloids[i] != InvalidOid && + 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 0000000..5c695e2 --- /dev/null +++ b/src/backend/catalog/storage_gtt.c @@ -0,0 +1,1491 @@ +/*------------------------------------------------------------------------- + * + * storage_gtt.c + * code to create and destroy physical storage for global temparary table + * + * IDENTIFICATION + * src/backend/catalog/storage_gtt.c + * + *------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/amapi.h" +#include "access/genam.h" +#include "access/htup_details.h" +#include "access/multixact.h" +#include "access/table.h" +#include "access/relation.h" +#include "access/visibilitymap.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "catalog/storage.h" +#include "catalog/storage_xlog.h" +#include "catalog/storage_gtt.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "catalog/index.h" +#include "catalog/pg_type.h" +#include "catalog/pg_statistic.h" +#include "commands/tablecmds.h" +#include "commands/sequence.h" +#include "funcapi.h" +#include "nodes/primnodes.h" +#include "nodes/pg_list.h" +#include "nodes/execnodes.h" +#include "miscadmin.h" +#include "storage/freespace.h" +#include "storage/smgr.h" +#include "storage/ipc.h" +#include "storage/proc.h" +#include "storage/procarray.h" +#include "storage/lwlock.h" +#include "storage/shmem.h" +#include "storage/sinvaladt.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/hsearch.h" +#include "utils/catcache.h" +#include "utils/lsyscache.h" +#include +#include "utils/inval.h" +#include "utils/guc.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_relstats_context = NULL; + +/* relfrozenxid of all gtts in the current session */ +static List *gtt_session_relfrozenxid_list = NIL; +static TransactionId gtt_session_frozenxid = InvalidTransactionId; + +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; + +typedef struct +{ + gtt_fnode rnode; + Bitmapset *map; + /* bitmap data */ +} gtt_shared_hash_entry; + +typedef struct +{ + Oid relfilenode; + Oid spcnode; + + /* pg_class stat */ + int32 relpages; + float4 reltuples; + int32 relallvisible; + TransactionId relfrozenxid; + TransactionId relminmxid; +} gtt_relfilenode; + +typedef struct +{ + Oid relid; + + List *relfilenode_list; + + char relkind; + bool on_commit_delete; + + /* pg_statistic */ + int natts; + int *attnum; + HeapTuple *att_stat_tups; + + Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */ +} 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 skiplock, 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_reset_statistics(gtt_local_hash_entry *entry); +static void gtt_free_statistics(gtt_local_hash_entry *entry); +static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok); +static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok); + +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); + + +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_size += MAXALIGN(sizeof(gtt_shared_hash_entry)); + hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1)); + + return hash_entry_size; +} + +Size +active_gtt_shared_hash_size(void) +{ + Size size = 0; + Size hash_entry_size = 0; + + if (max_active_gtt <= 0) + return 0; + + size = MAXALIGN(sizeof(gtt_ctl_data)); + hash_entry_size = action_gtt_shared_hash_entry_size(); + size += hash_estimate_size(max_active_gtt, hash_entry_size); + + return size; +} + +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_GTT_CTL, "gtt_shared_ctl"); + LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_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); +} + +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 == false) + { + int wordnum; + + 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; + } + + bms_add_member(entry->map, MyBackendId); + LWLockRelease(>t_shared_ctl->lock); +} + +static void +gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit) +{ + gtt_shared_hash_entry *entry; + gtt_fnode fnode; + + if (max_active_gtt <= 0) + return; + + fnode.dbNode = MyDatabaseId; + fnode.relNode = relid; + if (!skiplock) + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + + entry = hash_search(active_gtt_shared_hash, + (void *) &(fnode), HASH_FIND, NULL); + + if (entry == NULL) + { + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + if (isCommit) + elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid); + + return; + } + + Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends); + 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"); + } + + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + return; +} + +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 == NULL) + { + LWLockRelease(>t_shared_ctl->lock); + return NULL; + } + + Assert(entry->map); + if (!bms_is_empty(entry->map)) + map_copy = bms_copy(entry->map); + + LWLockRelease(>t_shared_ctl->lock); + + return map_copy; +} + +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); + + num_use = bms_num_members(entry->map); + if (num_use == 0) + in_use = false; + else if (num_use == 1) + { + 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; +} + +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); + + 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)); + + if (gtt_storage_local_hash == NULL) + { +#define GTT_LOCAL_HASH_SIZE 1024 + /* First time through: initialize the hash table */ + HASHCTL ctl; + + MemSet(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(Oid); + ctl.entrysize = sizeof(gtt_local_hash_entry); + gtt_storage_local_hash = + hash_create("global temporary table info", + GTT_LOCAL_HASH_SIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + gtt_relstats_context = + AllocSetContextCreate(CacheMemoryContext, + "gtt relstats context", + ALLOCSET_DEFAULT_SIZES); + } + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + + entry = gtt_search_by_relid(relid, true); + if (!entry) + { + bool found = false; + int natts = 0; + + /* 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; + entry->natts = 0; + entry->attnum = NULL; + entry->att_stat_tups = NULL; + entry->oldrelid = InvalidOid; + + natts = RelationGetNumberOfAttributes(rel); + entry->attnum = palloc0(sizeof(int) * natts); + entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts); + entry->natts = natts; + + if (entry->relkind == RELKIND_RELATION) + { + if (RELATION_GTT_ON_COMMIT_DELETE(rel)) + { + entry->on_commit_delete = true; + register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS); + } + } + + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + { + gtt_storage_checkin(relid); + } + } + + 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; + entry->relfilenode_list = lappend(entry->relfilenode_list, new_node); + + /* only heap contain transaction information */ + if (entry->relkind == RELKIND_RELATION) + { + new_node->relfrozenxid = RecentXmin; + new_node->relminmxid = GetOldestMultiXactId(); + insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + gtt_reset_statistics(entry); + + MemoryContextSwitchTo(oldcontext); + + if (!gtt_cleaner_exit_registered) + { + before_shmem_exit(gtt_storage_removeall, 0); + gtt_cleaner_exit_registered = true; + } + + return; +} + +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, true); + if (d_rnode == NULL) + { + if (isCommit) + elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid); + else if (entry->oldrelid != InvalidOid) + { + gtt_local_hash_entry *entry2 = NULL; + gtt_relfilenode *gttnode2 = NULL; + + entry2 = gtt_search_by_relid(entry->oldrelid, false); + gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false); + Assert(gttnode2->relfilenode == rnode.relNode); + Assert(list_length(entry->relfilenode_list) == 1); + /* rollback switch relfilenode */ + gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode, + entry->relid, gtt_fetch_current_relfilenode(entry->relid), + false); + /* clean up footprint */ + entry2->oldrelid = InvalidOid; + d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false); + Assert(d_rnode); + } + else + { + if (entry->relfilenode_list == NIL) + { + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, false, isCommit); + + gtt_free_statistics(entry); + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + + return; + } + } + + if (entry->relkind == RELKIND_RELATION) + { + Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit); + if (TransactionIdIsValid(d_rnode->relfrozenxid)) + { + remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + } + + entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode); + pfree(d_rnode); + if (entry->relfilenode_list == NIL) + { + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_SEQUENCE) + gtt_storage_checkout(relid, false, isCommit); + + if (isCommit && entry->oldrelid != InvalidOid) + { + gtt_local_hash_entry *entry2 = NULL; + + entry2 = gtt_search_by_relid(entry->oldrelid, false); + /* clean up footprint */ + entry2->oldrelid = InvalidOid; + } + + gtt_free_statistics(entry); + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + } + else + gtt_reset_statistics(entry); + + return; +} + +/* is the storage file was created in this backend */ +bool +gtt_storage_attached(Oid relid) +{ + bool found = false; + gtt_local_hash_entry *entry = NULL; + + if (max_active_gtt <= 0) + return false; + + entry = gtt_search_by_relid(relid, true); + if (entry) + found = true; + + return found; +} + +static void +gtt_storage_removeall(int code, Datum arg) +{ + HASH_SEQ_STATUS status; + gtt_local_hash_entry *entry; + SMgrRelation *srels = NULL; + Oid *relids = NULL; + char *relkinds = NULL; + int nrels = 0, + nfiles = 0, + maxrels = 0, + maxfiles = 0, + i = 0; + + if (gtt_storage_local_hash == NULL) + return; + + 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; + RelFileNode rnode; + gtt_relfilenode *gtt_rnode = lfirst(lc); + + rnode.spcNode = gtt_rnode->spcnode; + rnode.dbNode = MyDatabaseId; + rnode.relNode = gtt_rnode->relfilenode; + srel = smgropen(rnode, MyBackendId); + + if (maxfiles == 0) + { + maxfiles = 32; + srels = palloc(sizeof(SMgrRelation) * maxfiles); + } + else if (maxfiles <= nfiles) + { + maxfiles *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles); + } + + srels[nfiles++] = srel; + } + + if (maxrels == 0) + { + maxrels = 32; + relids = palloc(sizeof(Oid) * maxrels); + relkinds = palloc(sizeof(char) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + relids = repalloc(relids , sizeof(Oid) * maxrels); + relkinds = repalloc(relkinds, sizeof(char) * maxrels); + } + + relkinds[nrels] = entry->relkind; + relids[nrels] = entry->relid; + nrels++; + } + + if (nfiles > 0) + { + smgrdounlinkall(srels, nfiles, false); + for (i = 0; i < nfiles; i++) + smgrclose(srels[i]); + + pfree(srels); + } + + if (nrels) + { + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + for (i = 0; i < nrels; i++) + { + if (relkinds[i] == RELKIND_RELATION || + relkinds[i] == RELKIND_SEQUENCE) + gtt_storage_checkout(relids[i], true, false); + } + LWLockRelease(>t_shared_ctl->lock); + + pfree(relids); + pfree(relkinds); + } + + MyProc->session_gtt_frozenxid = InvalidTransactionId; + + return; +} + +/* + * Update global temp table relstats(relpage/reltuple/relallvisible) + * to local hashtable + */ +void +up_gtt_relstats(Relation relation, + BlockNumber num_pages, + double num_tuples, + BlockNumber num_all_visible_pages, + TransactionId relfrozenxid, + TransactionId relminmxid) +{ + Oid relid = RelationGetRelid(relation); + gtt_local_hash_entry *entry; + gtt_relfilenode *gtt_rnode = NULL; + + if (max_active_gtt <= 0) + 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 (num_pages >= 0 && + gtt_rnode->relpages != (int32)num_pages) + gtt_rnode->relpages = (int32)num_pages; + + if (num_tuples >= 0 && + gtt_rnode->reltuples != (float4)num_tuples) + gtt_rnode->reltuples = (float4)num_tuples; + + /* only heap contain transaction information and relallvisible */ + if (entry->relkind == RELKIND_RELATION) + { + if (gtt_rnode->relallvisible >= 0 && + gtt_rnode->relallvisible != (int32)num_all_visible_pages) + { + gtt_rnode->relallvisible = (int32)num_all_visible_pages; + } + + if (TransactionIdIsNormal(relfrozenxid) && + gtt_rnode->relfrozenxid != relfrozenxid && + (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) || + TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid))) + { + remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid); + gtt_rnode->relfrozenxid = relfrozenxid; + insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + if (MultiXactIdIsValid(relminmxid) && + gtt_rnode->relminmxid != relminmxid && + (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) || + MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid))) + { + gtt_rnode->relminmxid = relminmxid; + } + } + + return; +} + +/* + * Search global temp table relstats(relpage/reltuple/relallvisible) + * from local hashtable. + */ +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 global temp table statistic info(definition is same as pg_statistic) + * to local hashtable where ananyze global temp table + */ +void +up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts, + TupleDesc tupleDescriptor, Datum *values, bool *isnull) +{ + gtt_local_hash_entry *entry; + MemoryContext oldcontext; + int i = 0; + + if (max_active_gtt <= 0) + return; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return; + + if (entry->natts < natts) + { + elog(WARNING, "reloid %u not support update attstat after add colunm", reloid); + return; + } + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + Assert(entry->relid == reloid); + for (i = 0; i < entry->natts; i++) + { + if (entry->attnum[i] == 0) + { + entry->attnum[i] = attnum; + break; + } + else if (entry->attnum[i] == attnum) + { + Assert(entry->att_stat_tups[i]); + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + break; + } + } + + Assert(i < entry->natts); + Assert(entry->att_stat_tups[i] == NULL); + entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull); + MemoryContextSwitchTo(oldcontext); + + return; +} + +/* + * Search global temp table statistic info(definition is same as pg_statistic) + * from local hashtable. + */ +HeapTuple +get_gtt_att_statistic(Oid reloid, int attnum, bool inh) +{ + gtt_local_hash_entry *entry; + int i = 0; + + if (max_active_gtt <= 0) + return NULL; + + entry = gtt_search_by_relid(reloid, true); + if (entry == NULL) + return NULL; + + for (i = 0; i < entry->natts; i++) + { + if (entry->attnum[i] == attnum) + { + Assert(entry->att_stat_tups[i]); + return entry->att_stat_tups[i]; + } + } + + return NULL; +} + +void +release_gtt_statistic_cache(HeapTuple tup) +{ + /* do nothing */ + return; +} + +static void +insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid) +{ + MemoryContext oldcontext; + ListCell *cell; + int i; + + Assert(TransactionIdIsNormal(relfrozenxid)); + + oldcontext = MemoryContextSwitchTo(gtt_relstats_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; +} + +static void +remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid) +{ + gtt_session_relfrozenxid_list = + list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid); +} + +static void +set_gtt_session_relfrozenxid(void) +{ + TransactionId gtt_frozenxid = InvalidTransactionId; + + if (gtt_session_relfrozenxid_list) + gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list); + + gtt_session_frozenxid = gtt_frozenxid; + if (MyProc->session_gtt_frozenxid != gtt_frozenxid) + MyProc->session_gtt_frozenxid = gtt_frozenxid; +} + +Datum +pg_get_gtt_statistics(PG_FUNCTION_ARGS) +{ + HeapTuple tuple; + Relation rel = NULL; + int attnum = PG_GETARG_INT32(1); + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + MemoryContext oldcontext; + Tuplestorestate *tupstore; + 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); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + 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); + + tuple = get_gtt_att_statistic(reloid, attnum, false); + if (tuple) + { + Datum values[31]; + bool isnull[31]; + 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); + } + + relation_close(rel, NoLock); + relation_close(pg_tatistic, AccessShareLock); + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} + +Datum +pg_get_gtt_relstats(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + BlockNumber relpages = 0; + double reltuples = 0; + BlockNumber relallvisible = 0; + uint32 relfrozenxid = 0; + uint32 relminmxid = 0; + Oid relnode = 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); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + 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; +} + +Datum +pg_gtt_attached_pid(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + Oid reloid = PG_GETARG_OID(0); + char rel_persistence; + Relation rel = NULL; + PGPROC *proc = NULL; + Bitmapset *map = NULL; + pid_t pid = 0; + 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); + rel_persistence = get_rel_persistence(reloid); + if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP) + { + elog(WARNING, "relation OID %u is not a global temporary table", reloid); + relation_close(rel, NoLock); + return (Datum) 0; + } + + map = copy_active_gtt_bitmap(reloid); + if (map) + { + backendid = bms_first_member(map); + + do + { + proc = BackendIdGetProc(backendid); + pid = proc->pid; + if (pid > 0) + { + Datum values[2]; + bool isnull[2]; + + memset(isnull, false, 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, NoLock); + + return (Datum) 0; +} + +Datum +pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext oldcontext; + HeapTuple tuple; + int num_xid = MaxBackends + 1; + int *pids = NULL; + uint32 *xids = NULL; + int i = 0; + int j = 0; + uint32 oldest = 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); + + if (max_active_gtt <= 0) + return (Datum) 0; + + if (RecoveryInProgress()) + return (Datum) 0; + + pids = palloc0(sizeof(int) * num_xid); + xids = palloc0(sizeof(int) * num_xid); + oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i); + if (i > 0) + { + if (i > 0) + { + pids[i] = 0; + xids[i] = oldest; + i++; + } + + for(j = 0; j < i; j++) + { + Datum values[2]; + bool isnull[2]; + + memset(isnull, false, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = Int32GetDatum(pids[j]); + values[1] = UInt32GetDatum(xids[j]); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + } + tuplestore_donestoring(tupstore); + pfree(pids); + pfree(xids); + + return (Datum) 0; +} + +void +gtt_force_enable_index(Relation index) +{ + Oid indexOid = RelationGetRelid(index); + + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + Assert(index->rd_rel->relkind == RELKIND_INDEX); + Assert(OidIsValid(indexOid)); + + index->rd_index->indisvalid = true; + index->rd_index->indislive = true; + index->rd_index->indisready = true; +} + +void +gtt_fix_index_state(Relation index) +{ + Oid indexOid = RelationGetRelid(index); + Oid relOid = index->rd_index->indrelid; + + if (!RELATION_IS_GLOBAL_TEMP(index)) + return; + + if (!index->rd_index->indisvalid) + return; + + if (gtt_storage_attached(relOid) && + !gtt_storage_attached(indexOid)) + { + index->rd_index->indisvalid = false; + index->rd_index->indislive = false; + index->rd_index->indisready = false; + } + + return; +} + +void +init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo) +{ + Relation relation = resultRelInfo->ri_RelationDesc; + int i; + Oid toastrelid; + + if (operation != CMD_INSERT) + return; + + if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind)) + return; + + if (!RELATION_IS_GLOBAL_TEMP(relation)) + return; + + if (gtt_storage_attached(RelationGetRelid(relation))) + return; + + RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation); + + for (i = 0; i < resultRelInfo->ri_NumIndices; i++) + { + Relation index = resultRelInfo->ri_IndexRelationDescs[i]; + IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i]; + + Assert(index->rd_index->indisvalid); + Assert(index->rd_index->indislive); + Assert(index->rd_index->indisready); + + index_build(relation, index, info, true, false); + } + + toastrelid = relation->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + ListCell *indlist; + + toastrel = table_open(toastrelid, RowExclusiveLock); + RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel); + + foreach(indlist, RelationGetIndexList(toastrel)) + { + Oid indexId = lfirst_oid(indlist); + Relation currentIndex; + IndexInfo *indexInfo; + + currentIndex = index_open(indexId, RowExclusiveLock); + + indexInfo = BuildDummyIndexInfo(currentIndex); + index_build(toastrel, currentIndex, indexInfo, true, false); + index_close(currentIndex, NoLock); + } + + table_close(toastrel, NoLock); + } + + return; +} + +static void +gtt_reset_statistics(gtt_local_hash_entry *entry) +{ + int i; + + for (i = 0; i < entry->natts; i++) + { + if (entry->att_stat_tups[i]) + { + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + } + + entry->attnum[i] = 0; + } + + return; +} + +static void +gtt_free_statistics(gtt_local_hash_entry *entry) +{ + int i; + + for (i = 0; i < entry->natts; i++) + { + if (entry->att_stat_tups[i]) + { + heap_freetuple(entry->att_stat_tups[i]); + entry->att_stat_tups[i] = NULL; + } + } + + if (entry->attnum) + pfree(entry->attnum); + + if (entry->att_stat_tups) + pfree(entry->att_stat_tups); + + return; +} + +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; +} + +void +gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint) +{ + gtt_local_hash_entry *entry1; + gtt_local_hash_entry *entry2; + gtt_relfilenode *gtt_rnode1 = NULL; + gtt_relfilenode *gtt_rnode2 = NULL; + MemoryContext oldcontext; + + if (max_active_gtt <= 0) + return; + + if (gtt_storage_local_hash == NULL) + return; + + entry1 = gtt_search_by_relid(rel1, false); + gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false); + + entry2 = gtt_search_by_relid(rel2, false); + gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false); + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1); + entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1); + + entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2); + entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2); + MemoryContextSwitchTo(oldcontext); + + if (footprint) + { + entry1->oldrelid = rel2; + entry2->oldrelid = rel1; + } + + return; +} + +static gtt_relfilenode * +gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, 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) + { + rnode = gtt_rnode; + break; + } + } + + if (!missing_ok && rnode == NULL) + elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid); + + return rnode; +} + +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; +} + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 813ea8b..9bf81da 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -186,6 +186,91 @@ CREATE OR REPLACE 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 924ef37..db12eef 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" @@ -103,7 +104,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); @@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation, return; } + 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 */ @@ -586,14 +594,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])); } /* @@ -1456,7 +1465,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; @@ -1558,31 +1567,45 @@ 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)) + /* Update column statistic to localhash, not catalog */ + 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 04d12a7..8ac2d05 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/progress.h" @@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, bool *pSwapToastByContent, TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); static List *get_tables_to_cluster(MemoryContext cluster_context); +static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, + bool swap_toast_by_content, + bool is_internal, + TransactionId frozenXid, + MultiXactId cutoffMulti, + Oid *mapped_tables); /*--------------------------------------------------------------------------- @@ -366,6 +373,10 @@ cluster_rel(Oid tableOid, Oid indexOid, int options) errmsg("cannot vacuum temporary tables of other sessions"))); } + if (RELATION_IS_GLOBAL_TEMP(OldHeap) && + !gtt_storage_attached(RelationGetRelid(OldHeap))) + return; + /* * Also check for active uses of the relation in the current transaction, * including open scans and pending AFTER trigger events. @@ -750,6 +761,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, BlockNumber num_pages; int elevel = verbose ? INFO : DEBUG2; PGRUsage ru0; + bool is_gtt = false; + uint32 gtt_relfrozenxid = 0; + uint32 gtt_relminmxid = 0; pg_rusage_init(&ru0); @@ -763,6 +777,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, else OldIndex = NULL; + if (RELATION_IS_GLOBAL_TEMP(OldHeap)); + is_gtt = true; + /* * Their tuple descriptors should be exactly alike, but here we only need * assume that they have the same number of columns. @@ -830,20 +847,37 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff, NULL); - /* - * FreezeXid will become the table's new relfrozenxid, and that mustn't go - * backwards, so take the max. - */ - if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && - TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) - FreezeXid = OldHeap->rd_rel->relfrozenxid; + if (is_gtt) + { + get_gtt_relstats(OIDOldHeap, + NULL, NULL, NULL, + >t_relfrozenxid, >t_relminmxid); - /* - * MultiXactCutoff, similarly, shouldn't go backwards either. - */ - if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && - MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) - MultiXactCutoff = OldHeap->rd_rel->relminmxid; + if (TransactionIdIsValid(gtt_relfrozenxid) && + TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid)) + FreezeXid = gtt_relfrozenxid; + + if (MultiXactIdIsValid(gtt_relminmxid) && + MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid)) + MultiXactCutoff = gtt_relminmxid; + } + else + { + /* + * FreezeXid will become the table's new relfrozenxid, and that mustn't go + * backwards, so take the max. + */ + if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) && + TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid)) + FreezeXid = OldHeap->rd_rel->relfrozenxid; + + /* + * MultiXactCutoff, similarly, shouldn't go backwards either. + */ + if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) && + MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid)) + MultiXactCutoff = OldHeap->rd_rel->relminmxid; + } /* * Decide whether to use an indexscan or seqscan-and-optional-sort to scan @@ -911,6 +945,12 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose, table_close(OldHeap, NoLock); table_close(NewHeap, NoLock); + if (is_gtt) + { + CommandCounterIncrement(); + return; + } + /* Update pg_class to reflect the correct values of pages and tuples. */ relRelation = table_open(RelationRelationId, RowExclusiveLock); @@ -1346,10 +1386,20 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, * Swap the contents of the heap relations (including any toast tables). * Also set old heap's relfrozenxid to frozenXid. */ - swap_relation_files(OIDOldHeap, OIDNewHeap, + if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + gtt_swap_relation_files(OIDOldHeap, OIDNewHeap, + (OIDOldHeap == RelationRelationId), + swap_toast_by_content, is_internal, + frozenXid, cutoffMulti, mapped_tables); + } + else + { + swap_relation_files(OIDOldHeap, OIDNewHeap, (OIDOldHeap == RelationRelationId), swap_toast_by_content, is_internal, frozenXid, cutoffMulti, mapped_tables); + } /* * If it's a system catalog, queue a sinval message to flush all catcaches @@ -1557,3 +1607,141 @@ get_tables_to_cluster(MemoryContext cluster_context) return rvs; } + +static void +gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class, + bool swap_toast_by_content, + bool is_internal, + TransactionId frozenXid, + MultiXactId cutoffMulti, + Oid *mapped_tables) +{ + Relation relRelation; + Oid relfilenode1, + relfilenode2; + Relation rel1; + Relation rel2; + + relRelation = table_open(RelationRelationId, RowExclusiveLock); + + rel1 = relation_open(r1, AccessExclusiveLock); + rel2 = relation_open(r2, AccessExclusiveLock); + + relfilenode1 = gtt_fetch_current_relfilenode(r1); + relfilenode2 = gtt_fetch_current_relfilenode(r2); + + Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2)); + gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true); + + CacheInvalidateRelcache(rel1); + CacheInvalidateRelcache(rel2); + + InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0, + InvalidOid, is_internal); + InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0, + InvalidOid, true); + + if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid) + { + if (swap_toast_by_content) + { + if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid) + { + gtt_swap_relation_files(rel1->rd_rel->reltoastrelid, + rel2->rd_rel->reltoastrelid, + target_is_pg_class, + swap_toast_by_content, + is_internal, + frozenXid, + cutoffMulti, + mapped_tables); + } + else + elog(ERROR, "cannot swap toast files by content when there's only one"); + } + else + { + ObjectAddress baseobject, + toastobject; + long count; + + if (IsSystemRelation(rel1)) + elog(ERROR, "cannot swap toast files by links for system catalogs"); + + if (rel1->rd_rel->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelationRelationId, + rel1->rd_rel->reltoastrelid, + false); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } + if (rel2->rd_rel->reltoastrelid) + { + count = deleteDependencyRecordsFor(RelationRelationId, + rel2->rd_rel->reltoastrelid, + false); + if (count != 1) + elog(ERROR, "expected one dependency record for TOAST table, found %ld", + count); + } + + /* Register new dependencies */ + baseobject.classId = RelationRelationId; + baseobject.objectSubId = 0; + toastobject.classId = RelationRelationId; + toastobject.objectSubId = 0; + + if (rel1->rd_rel->reltoastrelid) + { + baseobject.objectId = r1; + toastobject.objectId = rel1->rd_rel->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, + DEPENDENCY_INTERNAL); + } + + if (rel2->rd_rel->reltoastrelid) + { + baseobject.objectId = r2; + toastobject.objectId = rel2->rd_rel->reltoastrelid; + recordDependencyOn(&toastobject, &baseobject, + DEPENDENCY_INTERNAL); + } + } + } + + if (swap_toast_by_content && + rel1->rd_rel->relkind == RELKIND_TOASTVALUE && + rel2->rd_rel->relkind == RELKIND_TOASTVALUE) + { + Oid toastIndex1, + toastIndex2; + + /* Get valid index for each relation */ + toastIndex1 = toast_get_valid_index(r1, + AccessExclusiveLock); + toastIndex2 = toast_get_valid_index(r2, + AccessExclusiveLock); + + gtt_swap_relation_files(toastIndex1, + toastIndex2, + target_is_pg_class, + swap_toast_by_content, + is_internal, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); + } + + relation_close(rel1, NoLock); + relation_close(rel2, NoLock); + + table_close(relRelation, RowExclusiveLock); + + RelationCloseSmgrByOid(r1); + RelationCloseSmgrByOid(r2); + + CommandCounterIncrement(); +} + diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index ac07f75..655184d 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -27,6 +27,7 @@ #include "catalog/dependency.h" #include "catalog/pg_authid.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "commands/copy.h" #include "commands/defrem.h" #include "commands/trigger.h" @@ -1063,7 +1064,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(rel)) PreventCommandIfReadOnly("COPY FROM"); cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program, @@ -2794,6 +2795,8 @@ CopyFrom(CopyState cstate) ExecOpenIndices(resultRelInfo, false); + init_gtt_storage(CMD_INSERT, resultRelInfo); + estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 2baca12..5338db9 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -521,6 +521,7 @@ DefineIndex(Oid relationId, Snapshot snapshot; int save_nestlevel = -1; int i; + char rel_persistence; /* * Some callers need us to run with an empty default_tablespace; this is a @@ -542,7 +543,9 @@ 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) + rel_persistence = get_rel_persistence(relationId); + if (stmt->concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) concurrent = true; else concurrent = false; @@ -2460,7 +2463,8 @@ ReindexIndex(RangeVar *indexRelation, int options, bool concurrent) persistence = irel->rd_rel->relpersistence; index_close(irel, NoLock); - if (concurrent && persistence != RELPERSISTENCE_TEMP) + if (concurrent && + !(persistence == RELPERSISTENCE_TEMP || persistence == RELPERSISTENCE_GLOBAL_TEMP)) ReindexRelationConcurrently(indOid, options); else reindex_index(indOid, false, persistence, @@ -2546,6 +2550,7 @@ ReindexTable(RangeVar *relation, int options, bool concurrent) { Oid heapOid; bool result; + char rel_persistence; /* * The lock level used here should match reindex_relation(). @@ -2560,7 +2565,9 @@ ReindexTable(RangeVar *relation, int options, bool concurrent) 0, RangeVarCallbackOwnsTable, NULL); - if (concurrent && get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP) + rel_persistence = get_rel_persistence(heapOid); + if (concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { result = ReindexRelationConcurrently(heapOid, options); @@ -2761,12 +2768,15 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, foreach(l, relids) { Oid relid = lfirst_oid(l); + char rel_persistence; StartTransactionCommand(); /* functions in indexes may want a snapshot set */ PushActiveSnapshot(GetTransactionSnapshot()); - if (concurrent && get_rel_persistence(relid) != RELPERSISTENCE_TEMP) + rel_persistence = get_rel_persistence(relid); + if (concurrent && + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { (void) ReindexRelationConcurrently(relid, options); /* ReindexRelationConcurrently() does the verbose output */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6aab73b..a45863a 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -30,6 +30,8 @@ #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" @@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */ */ static SeqTableData *last_used_seq = NULL; -static void fill_seq_with_data(Relation rel, HeapTuple tuple); +static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf); static Relation lock_and_open_sequence(SeqTable seq); static void create_seq_hashtable(void); static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel); @@ -108,6 +110,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); /* @@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq) /* now initialize the sequence's data */ tuple = heap_form_tuple(tupDesc, value, null); - fill_seq_with_data(rel, tuple); + fill_seq_with_data(rel, tuple, InvalidBuffer); /* process OWNED BY if given */ if (owned_by) @@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seq_rel, tuple); + fill_seq_with_data(seq_rel, tuple, InvalidBuffer); /* Clear local cache so that we don't think we have cached numbers */ /* Note that we do not change the currval() state */ @@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid) * Initialize a sequence's relation with the specified tuple as content */ static void -fill_seq_with_data(Relation rel, HeapTuple tuple) +fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf) { - Buffer buf; Page page; sequence_magic *sm; OffsetNumber offnum; + bool lockBuffer = false; /* Initialize first page of relation with special magic number */ - buf = ReadBuffer(rel, P_NEW); - Assert(BufferGetBlockNumber(buf) == 0); + if (buf == InvalidBuffer) + { + buf = ReadBuffer(rel, P_NEW); + Assert(BufferGetBlockNumber(buf) == 0); + lockBuffer = true; + } page = BufferGetPage(buf); @@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) /* Now insert sequence tuple */ - LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + if (lockBuffer) + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); /* * Since VACUUM does not process sequences, we have to force the tuple to @@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) END_CRIT_SECTION(); - UnlockReleaseBuffer(buf); + if (lockBuffer) + UnlockReleaseBuffer(buf); } /* @@ -451,6 +460,13 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) init_sequence(relid, &elm, &seqrel); + if (RELATION_IS_GLOBAL_TEMP(seqrel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(seqrel))) + elog(ERROR, "cannot alter global temp sequence %s when other backend attached it", + RelationGetRelationName(seqrel)); + } + rel = table_open(SequenceRelationId, RowExclusiveLock); seqtuple = SearchSysCacheCopy1(SEQRELID, ObjectIdGetDatum(relid)); @@ -502,7 +518,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt) /* * Insert the modified tuple into the new storage file. */ - fill_seq_with_data(seqrel, newdatatuple); + fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer); } /* process OWNED BY if given */ @@ -611,7 +627,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(seqrel)) PreventCommandIfReadOnly("nextval()"); /* @@ -936,7 +952,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(seqrel)) PreventCommandIfReadOnly("setval()"); /* @@ -1154,6 +1170,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel) /* Return results */ *p_elm = elm; *p_rel = seqrel; + + if (RELATION_IS_GLOBAL_TEMP(seqrel) && + !gtt_storage_attached(RelationGetRelid(seqrel))) + { + RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel); + gtt_init_seq(seqrel); + } } @@ -1954,3 +1977,46 @@ seq_mask(char *page, BlockNumber blkno) mask_unused_space(page); } + +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; +} + +void +gtt_init_seq(Relation rel) +{ + /* Initialize sequence for global temporary tables */ + Datum value[SEQ_COL_LASTCOL] = {0}; + bool null[SEQ_COL_LASTCOL] = {false}; + 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 */ + + tuple = heap_form_tuple(RelationGetDescr(rel), value, null); + fill_seq_with_data(rel, tuple, InvalidBuffer); + heap_freetuple(tuple); + + return; +} + diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 6162fb0..e825c04 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" @@ -555,6 +556,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl); static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static OnCommitAction gtt_oncommit_option(List *options); /* ---------------------------------------------------------------- @@ -600,6 +602,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 @@ -610,8 +613,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Check consistency of arguments */ + /* global temp table same as local temp table */ if (stmt->oncommit != ONCOMMIT_NOOP - && stmt->relation->relpersistence != RELPERSISTENCE_TEMP) + && !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP || + stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)) ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("ON COMMIT can only be used on temporary tables"))); @@ -641,7 +646,9 @@ 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 + /* global temp table same as local temp table */ + if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP || + stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) && InSecurityRestrictedOperation()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -742,6 +749,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ + /* global temp table */ + oncommit_action = gtt_oncommit_option(stmt->options); + if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && + (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) + { + /* check parent table*/ + if (inheritOids) + { + Oid parent = linitial_oid(inheritOids); + Relation relation = table_open(parent, NoLock); + + if (!RELATION_IS_GLOBAL_TEMP(relation)) + elog(ERROR, "The parent table must be global temporary table"); + + table_close(relation, NoLock); + } + + if (oncommit_action != ONCOMMIT_NOOP) + { + if (stmt->oncommit != ONCOMMIT_NOOP) + elog(ERROR, "could not create global temporary table with on commit and with clause at same time"); + + 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 + elog(ERROR, "global temp table not support on commit drop clause"); + + stmt->options = lappend(stmt->options, opt); + } + } + else if (oncommit_action != ONCOMMIT_NOOP) + elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table"); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, true, false); @@ -1329,6 +1385,7 @@ RemoveRelations(DropStmt *drop) Oid relOid; ObjectAddress obj; struct DropRelationCallbackState state; + char rel_persistence; /* * These next few steps are a great deal like relation_openrv, but we @@ -1362,8 +1419,9 @@ RemoveRelations(DropStmt *drop) * Decide if concurrent mode needs to be used here or not. The * relation persistence cannot be known without its OID. */ + rel_persistence = get_rel_persistence(relOid); if (drop->concurrent && - get_rel_persistence(relOid) != RELPERSISTENCE_TEMP) + !(rel_persistence == RELPERSISTENCE_TEMP || rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)) { Assert(list_length(drop->objects) == 1 && drop->removeType == OBJECT_INDEX); @@ -1818,6 +1876,10 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) continue; + if (RELATION_IS_GLOBAL_TEMP(rel) && + !gtt_storage_attached(RelationGetRelid(rel))) + continue; + /* * Normally, we need a transaction-safe truncation here. However, if * the table was either created in the current (sub)transaction or has @@ -3569,6 +3631,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode, /* Caller is required to provide an adequate lock. */ rel = relation_open(context->relid, NoLock); + /* We allow to alter global temp table only this session use it */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + if (is_other_backend_use_gtt(RelationGetRelid(rel))) + elog(ERROR, "can not alter table %s when other backend attached this global temp table", + RelationGetRelationName(rel)); + } + CheckTableNotInUse(rel, "ALTER TABLE"); ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); @@ -4846,6 +4916,12 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot rewrite temporary tables of other sessions"))); + if (RELATION_IS_GLOBAL_TEMP(OldHeap) && + tab->chgPersistence) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot change global temporary table persistence setting"))); + /* * Select destination tablespace (same as original unless user * requested a change) @@ -8183,6 +8259,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; } /* @@ -12739,6 +12821,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, if (defList == NIL && operation != AT_ReplaceRelOptions) return; /* nothing to do */ + if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP) + elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command."); + pgclass = table_open(RelationRelationId, RowExclusiveLock); /* Fetch heap tuple */ @@ -12941,6 +13026,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode) */ rel = relation_open(tableOid, lockmode); + if (RELATION_IS_GLOBAL_TEMP(rel)) + elog(ERROR, "not support alter table set tablespace on global temp table"); + /* * No work if no change in tablespace. */ @@ -13315,7 +13403,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(rel->rd_smgr, dstrel, MAIN_FORKNUM, @@ -14722,7 +14810,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged) */ switch (rel->rd_rel->relpersistence) { + /* global temp table same as local temp table */ 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", @@ -17315,3 +17405,36 @@ ATDetachCheckNoForeignKeyRefs(Relation partition) table_close(rel, NoLock); } } + +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); + + 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 3a89f8f..532f961 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -35,6 +35,7 @@ #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/defrem.h" #include "commands/vacuum.h" @@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation, HeapTuple ctup; Form_pg_class pgcform; bool dirty; + bool is_gtt = false; + + /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */ + if (RELATION_IS_GLOBAL_TEMP(relation)) + { + is_gtt = true; + up_gtt_relstats(relation, + num_pages, num_tuples, + num_all_visible_pages, + frozenxid, minmulti); + } rd = table_open(RelationRelationId, RowExclusiveLock); @@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation, /* Apply statistical updates, if any, to copied tuple */ dirty = false; - if (pgcform->relpages != (int32) num_pages) + + if (is_gtt) + relation->rd_rel->relpages = (int32) num_pages; + else if (pgcform->relpages != (int32) num_pages) { pgcform->relpages = (int32) num_pages; dirty = true; } - if (pgcform->reltuples != (float4) num_tuples) + + if (is_gtt) + relation->rd_rel->reltuples = (float4) num_tuples; + else if (pgcform->reltuples != (float4) num_tuples) { pgcform->reltuples = (float4) num_tuples; dirty = true; } - if (pgcform->relallvisible != (int32) num_all_visible_pages) + + if (is_gtt) + relation->rd_rel->relallvisible = (int32) num_all_visible_pages; + else if (pgcform->relallvisible != (int32) num_all_visible_pages) { pgcform->relallvisible = (int32) num_all_visible_pages; dirty = true; @@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation, * This should match vac_update_datfrozenxid() concerning what we consider * to be "in the future". */ - if (TransactionIdIsNormal(frozenxid) && + if (!is_gtt && + TransactionIdIsNormal(frozenxid) && pgcform->relfrozenxid != frozenxid && (TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) || TransactionIdPrecedes(ReadNewTransactionId(), @@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation, } /* Similarly for relminmxid */ - if (MultiXactIdIsValid(minmulti) && + if (!is_gtt && + MultiXactIdIsValid(minmulti) && pgcform->relminmxid != minmulti && (MultiXactIdPrecedes(pgcform->relminmxid, minmulti) || MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid))) @@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void) continue; } + /* global temp table relstats not in pg_class */ + 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 @@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void) Assert(TransactionIdIsNormal(newFrozenXid)); Assert(MultiXactIdIsValid(newMinMulti)); + /* + * Global temp table get frozenxid from MyProc + * to avoid the vacuum truncate clog that gtt need. + */ + if (max_active_gtt > 0) + { + TransactionId oldest_gtt_frozenxid = + list_all_session_gtt_frozenxids(0, NULL, NULL, NULL); + + if (TransactionIdIsNormal(oldest_gtt_frozenxid) && + TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid)) + { + ereport(WARNING, + (errmsg("global temp table oldest FrozenXid is far in the past"), + errhint("please truncate them or kill those sessions that use them."))); + newFrozenXid = oldest_gtt_frozenxid; + } + } + /* Now fetch the pg_database tuple we need to update. */ relation = table_open(DatabaseRelationId, RowExclusiveLock); @@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) return false; } + if (RELATION_IS_GLOBAL_TEMP(onerel) && + !gtt_storage_attached(RelationGetRelid(onerel))) + { + ereport(WARNING, + (errmsg("skipping vacuum global temp table \"%s\" because storage is not initialized for current session", + RelationGetRelationName(onerel)))); + relation_close(onerel, 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 6e65103..04706ee 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -530,6 +530,11 @@ DefineView(ViewStmt *stmt, const char *queryString, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("views cannot be unlogged because they do not have storage"))); + if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("views cannot be global temp 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 4fdffad..0aa7559 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt) if (isTempNamespace(get_rel_namespace(rte->relid))) continue; + if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP) + continue; + PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt)); } diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c index fb6ce49..06d9237 100644 --- a/src/backend/executor/execPartition.c +++ b/src/backend/executor/execPartition.c @@ -18,6 +18,7 @@ #include "catalog/partition.h" #include "catalog/pg_inherits.h" #include "catalog/pg_type.h" +#include "catalog/storage_gtt.h" #include "executor/execPartition.h" #include "executor/executor.h" #include "foreign/fdwapi.h" @@ -545,6 +546,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate, (node != NULL && node->onConflictAction != ONCONFLICT_NONE)); + init_gtt_storage(mtstate->operation, leaf_part_rri); + /* * Build WITH CHECK OPTION constraints for the partition. Note that we * didn't build the withCheckOptionList for partitions within the planner, diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index d71c0a4..0900907 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -42,6 +42,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" @@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) ExecOpenIndices(resultRelInfo, node->onConflictAction != ONCONFLICT_NONE); + init_gtt_storage(operation, resultRelInfo); + /* * If this is an UPDATE and a BEFORE UPDATE trigger is present, the * trigger itself might modify the partition-key values. So arrange diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index ccf46dd..5deb0fc 100644 --- a/src/backend/optimizer/path/allpaths.c +++ b/src/backend/optimizer/path/allpaths.c @@ -591,6 +591,8 @@ static void set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte) { + char relpersistence; + /* * The flag has previously been initialized to false, so we can just * return if it becomes clear that we can't safely set it. @@ -618,7 +620,11 @@ 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) + /* global temp table is same as local temp table */ + relpersistence = get_rel_persistence(rte->relid); + + if (relpersistence == RELPERSISTENCE_TEMP || + relpersistence == RELPERSISTENCE_GLOBAL_TEMP) return; /* diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c index aeb8384..e3e4891 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6358,7 +6358,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid) * Furthermore, any index predicate or index expressions must be parallel * safe. */ + /* global temp table is same as local temp table */ if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP || + heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || !is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) || !is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index))) { diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index 2554502..04fb5d4 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -28,6 +28,7 @@ #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" +#include "catalog/storage_gtt.h" #include "catalog/pg_am.h" #include "catalog/pg_proc.h" #include "catalog/pg_statistic_ext.h" @@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, continue; } + /* Ignore empty index for global temp table */ + 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 6676412..0685c1c 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("materialized views must not use temporary tables or views"))); + if (is_query_using_gtt(query)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use global temporary tables or views"))); + /* * A materialized view would either need to save parameters for use in * maintaining/loading the data or prohibit them entirely. The latter diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 3449c26..e8fe9d3 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3293,17 +3293,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; } @@ -11629,19 +11623,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 b875a50..123945b 100644 --- a/src/backend/parser/parse_relation.c +++ b/src/backend/parser/parse_relation.c @@ -81,6 +81,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 is_query_using_gtt_walker(Node *node, void *context); /* @@ -3579,3 +3580,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context) isQueryUsingTempRelation_walker, context); } + +/* check if the query uses global temp table */ +static bool +is_query_using_gtt_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 = relation_open(rte->relid, AccessShareLock); + char relpersistence = rel->rd_rel->relpersistence; + + relation_close(rel, AccessShareLock); + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + return true; + } + } + + return query_tree_walker(query, + is_query_using_gtt_walker, + context, + QTW_IGNORE_JOINALIASES); + } + + return expression_tree_walker(node, + is_query_using_gtt_walker, + context); +} + +/* check if the query uses global temp table */ +bool +is_query_using_gtt(Query *query) +{ + return is_query_using_gtt_walker((Node *) query, NULL); +} + diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c index 2b9598c..1a481ff 100644 --- a/src/backend/parser/parse_utilcmd.c +++ b/src/backend/parser/parse_utilcmd.c @@ -437,6 +437,9 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column, seqstmt->sequence = makeRangeVar(snamespace, sname, -1); seqstmt->options = seqoptions; + 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 @@ -3089,6 +3092,7 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt, cxt.isforeign = false; } cxt.relation = stmt->relation; + 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 7e97ffa..1f2ac58 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2089,6 +2089,11 @@ do_autovacuum(void) } continue; } + else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + /* autovacuum skip vacuum global temp table */ + continue; + } /* Fetch reloptions and the pgstat entry for this table */ relopts = extract_autovac_opts(tuple, pg_class_desc); @@ -2155,7 +2160,9 @@ do_autovacuum(void) /* * We cannot safely process other backends' temp tables, so skip 'em. */ - if (classForm->relpersistence == RELPERSISTENCE_TEMP) + /* autovacuum skip vacuum global temp table */ + if (classForm->relpersistence == RELPERSISTENCE_TEMP || + classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) continue; relid = classForm->oid; diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c index 7317ac8..2d0b663 100644 --- a/src/backend/storage/buffer/bufmgr.c +++ b/src/backend/storage/buffer/bufmgr.c @@ -37,6 +37,7 @@ #include "access/xlog.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" @@ -53,6 +54,7 @@ #include "utils/rel.h" #include "utils/resowner_private.h" #include "utils/timestamp.h" +#include "utils/guc.h" /* Note: these two macros only work on shared buffers, not local ones! */ @@ -2770,6 +2772,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln) BlockNumber RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum) { + /* + * When this backend not init gtt storage + * return 0 + */ + if (RELATION_IS_GLOBAL_TEMP(relation) && + !gtt_storage_attached(RelationGetRelid(relation))) + { + return 0; + } + switch (relation->rd_rel->relkind) { case RELKIND_SEQUENCE: diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 427b0d5..7adb969 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -21,6 +21,7 @@ #include "access/nbtree.h" #include "access/subtrans.h" #include "access/twophase.h" +#include "catalog/storage_gtt.h" #include "commands/async.h" #include "miscadmin.h" #include "pgstat.h" @@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void) 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 @@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void) SUBTRANSShmemInit(); MultiXactShmemInit(); InitBufferPool(); + /* global temporary table */ + 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 281fe67..b964d80 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -63,6 +63,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)))) @@ -4088,3 +4089,77 @@ KnownAssignedXidsReset(void) LWLockRelease(ProcArrayLock); } + +/* + * search all active backend to get oldest frozenxid + * for global temp table. + */ +int +list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n) +{ + ProcArrayStruct *arrayP = procArray; + TransactionId result = InvalidTransactionId; + int index; + int flags = 0; + int i = 0; + + if (max_active_gtt <= 0) + return 0; + + if (max_size > 0) + { + Assert(pids); + Assert(xids); + Assert(n); + *n = 0; + } + + if (max_active_gtt <= 0) + return InvalidTransactionId; + + if (RecoveryInProgress()) + return InvalidTransactionId; + + flags |= PROC_IS_AUTOVACUUM; + flags |= PROC_IN_LOGICAL_DECODING; + + LWLockAcquire(ProcArrayLock, LW_SHARED); + if (max_size > 0 && max_size < arrayP->numProcs) + { + LWLockRelease(ProcArrayLock); + elog(ERROR, "list_all_gtt_frozenxids require more array"); + } + + for (index = 0; index < arrayP->numProcs; index++) + { + int pgprocno = arrayP->pgprocnos[index]; + volatile PGPROC *proc = &allProcs[pgprocno]; + volatile PGXACT *pgxact = &allPgXact[pgprocno]; + + if (pgxact->vacuumFlags & flags) + continue; + + if (proc->databaseId == MyDatabaseId && + TransactionIdIsNormal(proc->session_gtt_frozenxid)) + { + if (result == InvalidTransactionId) + result = proc->session_gtt_frozenxid; + else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result)) + result = proc->session_gtt_frozenxid; + + if (max_size > 0) + { + pids[i] = proc->pid; + xids[i] = proc->session_gtt_frozenxid; + i++; + } + } + } + LWLockRelease(ProcArrayLock); + + if (max_size > 0) + *n = i; + + return result; +} + diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 9938cdd..231750b 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -395,6 +395,7 @@ InitProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyPgXact->delayChkpt = false; MyPgXact->vacuumFlags = 0; @@ -577,6 +578,7 @@ InitAuxiliaryProcess(void) MyProc->databaseId = InvalidOid; MyProc->roleId = InvalidOid; MyProc->tempNamespaceId = InvalidOid; + MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */ MyProc->isBackgroundWorker = IsBackgroundWorker; MyPgXact->delayChkpt = false; MyPgXact->vacuumFlags = 0; diff --git a/src/backend/utils/adt/bool.c b/src/backend/utils/adt/bool.c index 340607f..6e7368d 100644 --- a/src/backend/utils/adt/bool.c +++ b/src/backend/utils/adt/bool.c @@ -39,7 +39,7 @@ parse_bool_with_len(const char *value, size_t len, bool *result) { case 't': case 'T': - if (pg_strncasecmp(value, "true", len) == 0) + if (len == 1 || (len == 4 && pg_strncasecmp(value, "true", len) == 0)) { if (result) *result = true; @@ -48,7 +48,7 @@ parse_bool_with_len(const char *value, size_t len, bool *result) break; case 'f': case 'F': - if (pg_strncasecmp(value, "false", len) == 0) + if (len == 1 || (len == 5 && pg_strncasecmp(value, "false", len) == 0)) { if (result) *result = false; @@ -57,7 +57,7 @@ parse_bool_with_len(const char *value, size_t len, bool *result) break; case 'y': case 'Y': - if (pg_strncasecmp(value, "yes", len) == 0) + if (len == 1 || (len == 3 && pg_strncasecmp(value, "yes", len) == 0)) { if (result) *result = true; diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index 8406644..0d99321 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -1008,6 +1008,9 @@ pg_relation_filepath(PG_FUNCTION_ARGS) Assert(backend != InvalidBackendId); } break; + case RELPERSISTENCE_GLOBAL_TEMP: + 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 4fdcb07..58fc8de 100644 --- a/src/backend/utils/adt/selfuncs.c +++ b/src/backend/utils/adt/selfuncs.c @@ -112,6 +112,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" @@ -4731,12 +4732,25 @@ 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) + { + 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)) { @@ -4861,15 +4875,27 @@ 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) + { + 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)) { @@ -6251,6 +6277,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; @@ -6268,6 +6295,13 @@ 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) + { + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + rte->inh); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -6279,6 +6313,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; @@ -6294,6 +6330,13 @@ 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) + { + vardata.statsTuple = get_gtt_att_statistic(relid, + colnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = SearchSysCache3(STATRELATTINH, @@ -7212,6 +7255,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)) @@ -7224,6 +7269,14 @@ 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) + { + vardata.statsTuple = + get_gtt_att_statistic(rte->relid, + attnum, + false); + vardata.freefunc = release_gtt_statistic_cache; + } else { vardata.statsTuple = @@ -7236,6 +7289,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. @@ -7255,6 +7310,14 @@ 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) + { + 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 a7d63f1..d8a52cb 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" @@ -2939,6 +2940,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum) if (stawidth > 0) return stawidth; } + if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP) + { + 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 dfd81f1..d1a32e1 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -64,6 +64,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" @@ -1132,6 +1133,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) relation->rd_islocaltemp = false; } break; + case RELPERSISTENCE_GLOBAL_TEMP: + { + BlockNumber relpages = 0; + double reltuples = 0; + BlockNumber relallvisible = 0; + + relation->rd_backend = BackendIdForTempRelations(); + relation->rd_islocaltemp = false; + get_gtt_relstats(RelationGetRelid(relation), + &relpages, + &reltuples, + &relallvisible, + NULL, NULL); + relation->rd_rel->relpages = (int32)relpages; + relation->rd_rel->reltuples = (float4)reltuples; + relation->rd_rel->relallvisible = (int32)relallvisible; + } + break; default: elog(ERROR, "invalid relpersistence: %c", relation->rd_rel->relpersistence); @@ -1186,6 +1205,7 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) case RELKIND_PARTITIONED_INDEX: Assert(relation->rd_rel->relam != InvalidOid); RelationInitIndexAccessInfo(relation); + gtt_fix_index_state(relation); break; case RELKIND_RELATION: case RELKIND_TOASTVALUE: @@ -1316,7 +1336,17 @@ RelationInitPhysicalAddr(Relation relation) heap_freetuple(phys_tuple); } - relation->rd_node.relNode = relation->rd_rel->relfilenode; + if (RELATION_IS_GLOBAL_TEMP(relation)) + { + Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation)); + if (newrelnode != InvalidOid && + 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 { @@ -2245,6 +2275,8 @@ RelationReloadIndexInfo(Relation relation) HeapTupleHeaderGetXmin(tuple->t_data)); ReleaseSysCache(tuple); + + gtt_fix_index_state(relation); } /* Okay, now it's valid again */ @@ -3471,6 +3503,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; @@ -3578,28 +3614,34 @@ void RelationSetNewRelfilenode(Relation relation, char persistence) { Oid newrelfilenode; - Relation pg_class; - HeapTuple tuple; + Relation pg_class = NULL; + HeapTuple tuple = NULL; Form_pg_class classform; MultiXactId minmulti = InvalidMultiXactId; TransactionId freezeXid = InvalidTransactionId; RelFileNode newrnode; + bool modify_pg_class = !RELATION_IS_GLOBAL_TEMP(relation); /* Allocate a new relfilenode */ newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL, persistence); - /* - * Get a writable copy of the pg_class tuple for the given relation. - */ - pg_class = table_open(RelationRelationId, RowExclusiveLock); + if (modify_pg_class) + { + /* + * Get a writable copy of the pg_class tuple for the given relation. + */ + pg_class = table_open(RelationRelationId, RowExclusiveLock); - tuple = SearchSysCacheCopy1(RELOID, - ObjectIdGetDatum(RelationGetRelid(relation))); - if (!HeapTupleIsValid(tuple)) - elog(ERROR, "could not find tuple for relation %u", - RelationGetRelid(relation)); - classform = (Form_pg_class) GETSTRUCT(tuple); + tuple = SearchSysCacheCopy1(RELOID, + ObjectIdGetDatum(RelationGetRelid(relation))); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "could not find tuple for relation %u", + RelationGetRelid(relation)); + classform = (Form_pg_class) GETSTRUCT(tuple); + } + else + memset(&classform, 0, sizeof(classform)); /* * Schedule unlinking of the old storage at transaction commit. @@ -3625,7 +3667,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); } break; @@ -3645,6 +3687,15 @@ RelationSetNewRelfilenode(Relation relation, char persistence) break; } + if (!modify_pg_class) + { + Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation)); + + Assert(RELATION_IS_GLOBAL_TEMP(relation)); + Assert(!RelationIsMapped(relation)); + relation->rd_node.relNode = relnode; + CacheInvalidateRelcache(relation); + } /* * If we're dealing with a mapped index, pg_class.relfilenode doesn't * change; instead we have to send the update to the relation mapper. @@ -3654,7 +3705,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence) * possibly-inaccurate values of relpages etc, but those will be fixed up * later. */ - if (RelationIsMapped(relation)) + else if (RelationIsMapped(relation)) { /* This case is only supported for indexes */ Assert(relation->rd_rel->relkind == RELKIND_INDEX); @@ -3700,9 +3751,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence) CatalogTupleUpdate(pg_class, &tuple->t_self, tuple); } - heap_freetuple(tuple); + if (modify_pg_class) + { + heap_freetuple(tuple); - table_close(pg_class, RowExclusiveLock); + table_close(pg_class, RowExclusiveLock); + } /* * Make the pg_class row change or relation map change visible. This will diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 03a22d7..96b4c39 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -142,6 +142,18 @@ char *GUC_check_errmsg_string; char *GUC_check_errdetail_string; char *GUC_check_errhint_string; +/* + * num = 0 means disable global temp table feature. + * global temp table define can still storage in catalog + * just can not use. + * num > 0 means database can management num active global temp table. + */ +#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, @@ -2070,6 +2082,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("Forces a switch to the next WAL file if a " "new file has not been started within N seconds."), diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 408637c..8c761f3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -2424,6 +2424,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo) dopt->no_unlogged_table_data) return; + /* Don't dump data in global temp 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)) @@ -15756,6 +15760,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) char *ftoptions = NULL; char *srvname = NULL; char *foreign = ""; + char *table_type = NULL; switch (tbinfo->relkind) { @@ -15809,9 +15814,15 @@ dumpTableSchema(Archive *fout, 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); @@ -17110,6 +17121,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) PQExpBuffer query = createPQExpBuffer(); PQExpBuffer delqry = createPQExpBuffer(); char *qseqname; + bool global_temp_seq = false; qseqname = pg_strdup(fmtId(tbinfo->dobj.name)); @@ -17119,9 +17131,11 @@ dumpSequence(Archive *fout, 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 if (fout->remoteVersion >= 80400) @@ -17166,6 +17180,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo) cache = PQgetvalue(res, 0, 5); cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0); + if (fout->remoteVersion >= 130000) + 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) @@ -17240,9 +17257,13 @@ dumpSequence(Archive *fout, 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 00aef85..9ad71c8 100644 --- a/src/bin/pg_upgrade/check.c +++ b/src/bin/pg_upgrade/check.c @@ -84,7 +84,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(); @@ -157,7 +157,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 7e524ea..371168f 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_gtt); static void free_rel_infos(RelInfoArr *rel_arr); static void print_db_infos(DbInfoArr *dbinfo); static void print_rel_infos(RelInfoArr *rel_arr); @@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name) * * 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_gtt) { int dbnum; @@ -316,7 +318,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_gtt); if (cluster == &old_cluster) pg_log(PG_VERBOSE, "\nsource databases:\n"); @@ -404,7 +406,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_gtt) { PGconn *conn = connectToServer(cluster, dbinfo->db_name); @@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo) * output, so we have to copy that system table. It's easiest to do that * by treating it as a user table. */ - snprintf(query + strlen(query), sizeof(query) - strlen(query), - "WITH regular_heap (reloid, indtable, toastheap) AS ( " - " SELECT c.oid, 0::oid, 0::oid " - " 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 " - /* exclude possible orphaned temp tables */ - " ((n.nspname !~ '^pg_temp_' AND " - " n.nspname !~ '^pg_toast_temp_' AND " - " n.nspname NOT IN ('pg_catalog', 'information_schema', " - " 'binary_upgrade', 'pg_toast') AND " - " c.oid >= %u::pg_catalog.oid) OR " - " (n.nspname = 'pg_catalog' AND " - " relname IN ('pg_largeobject') ))), ", - FirstNormalObjectId); + if (skip_gtt) + { + snprintf(query + strlen(query), sizeof(query) - strlen(query), + "WITH regular_heap (reloid, indtable, toastheap) AS ( " + " SELECT c.oid, 0::oid, 0::oid " + " 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 " + /* exclude global temp tables */ + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + /* exclude possible orphaned temp tables */ + " ((n.nspname !~ '^pg_temp_' AND " + " n.nspname !~ '^pg_toast_temp_' AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema', " + " 'binary_upgrade', 'pg_toast') AND " + " c.oid >= %u::pg_catalog.oid) OR " + " (n.nspname = 'pg_catalog' AND " + " relname IN ('pg_largeobject') ))), ", + FirstNormalObjectId); + } + else + { + snprintf(query + strlen(query), sizeof(query) - strlen(query), + "WITH regular_heap (reloid, indtable, toastheap) AS ( " + " SELECT c.oid, 0::oid, 0::oid " + " 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 " + /* exclude possible orphaned temp tables */ + " ((n.nspname !~ '^pg_temp_' AND " + " n.nspname !~ '^pg_toast_temp_' AND " + " n.nspname NOT IN ('pg_catalog', 'information_schema', " + " 'binary_upgrade', 'pg_toast') AND " + " c.oid >= %u::pg_catalog.oid) OR " + " (n.nspname = 'pg_catalog' AND " + " relname IN ('pg_largeobject') ))), ", + FirstNormalObjectId); + } /* * Add a CTE that collects OIDs of toast tables belonging to the tables diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c index 70194eb..8dd9e66 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); } /* @@ -642,7 +642,9 @@ 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 " + " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND " + "relkind IN (" CppAsString2(RELKIND_RELATION) ", " CppAsString2(RELKIND_MATVIEW) ", " CppAsString2(RELKIND_TOASTVALUE) ")", @@ -653,7 +655,9 @@ 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 " + " 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 8b90cef..d155205 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -389,7 +389,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_gtt); void print_maps(FileNameMap *maps, int n, const char *db_name); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 109245f..557cce1 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3724,7 +3724,8 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys if (pset.sversion >= 90100) { 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 0e7a373..a47de01 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1023,6 +1023,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}, @@ -2390,6 +2392,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 INDEX --- is allowed inside CREATE SCHEMA, so use TailMatches */ /* First off we complete CREATE UNIQUE with "INDEX" */ @@ -2598,6 +2603,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"); /* Complete "CREATE UNLOGGED" with TABLE or MATVIEW */ else if (TailMatches("CREATE", "UNLOGGED")) COMPLETE_WITH("TABLE", "MATERIALIZED VIEW"); diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index 78b33b2..4be89f6 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class; #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 2d1862a..152e4a4 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5558,6 +5558,40 @@ proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_xact_function_self_time' }, +# For global temporary table +{ oid => '4388', + 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 => '4389', + 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 => '4390', + 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 => '4391', + 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 30c38e0..7ff2408 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 0000000..45fd470 --- /dev/null +++ b/src/include/catalog/storage_gtt.h @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- + * + * 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 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 Bitmapset *copy_active_gtt_bitmap(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 up_gtt_relstats(Relation relation, + BlockNumber num_pages, + double num_tuples, + BlockNumber num_all_visible_pages, + TransactionId relfrozenxid, + TransactionId relminmxid); +extern bool get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples, + BlockNumber *relallvisible, TransactionId *relfrozenxid, + TransactionId *relminmxid); +extern void gtt_force_enable_index(Relation index); +extern void gtt_fix_index_state(Relation index); +extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo); +extern Oid gtt_fetch_current_relfilenode(Oid relid); +extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint); + +#endif /* STORAGE_H */ diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h index e2638ab..89a5ce4 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_seq(Relation rel); #endif /* SEQUENCE_H */ diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h index 93f9446..14cafae 100644 --- a/src/include/parser/parse_relation.h +++ b/src/include/parser/parse_relation.h @@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid); extern Oid attnumCollationId(Relation rd, int attid); extern bool isQueryUsingTempRelation(Query *query); +/* global temp table check */ +extern bool is_query_using_gtt(Query *query); + #endif /* PARSE_RELATION_H */ diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h index 3f88683..f870e9a 100644 --- a/src/include/storage/bufpage.h +++ b/src/include/storage/bufpage.h @@ -405,6 +405,8 @@ do { \ #define PageClearPrunable(page) \ (((PageHeader) (page))->pd_prune_xid = InvalidTransactionId) +#define GlobalTempRelationPageIsNotInitialized(rel, page) \ + ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page)) /* ---------------------------------------------------------------- * extern declarations diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h index 8fda8e4..172eae9 100644 --- a/src/include/storage/lwlock.h +++ b/src/include/storage/lwlock.h @@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds LWTRANCHE_TBM, LWTRANCHE_PARALLEL_APPEND, LWTRANCHE_SXACT, + LWTRANCHE_GTT_CTL, LWTRANCHE_FIRST_USER_DEFINED } BuiltinTrancheIds; diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index d217801..8adde87 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -117,6 +117,8 @@ struct PGPROC Oid tempNamespaceId; /* OID of temp schema this backend is * using */ + TransactionId session_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 a5c7d0c..a86f61f 100644 --- a/src/include/storage/procarray.h +++ b/src/include/storage/procarray.h @@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin, extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin, TransactionId *catalog_xmin); +extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n); + #endif /* PROCARRAY_H */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 2819282..363fc33 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -282,6 +282,10 @@ extern int tcp_user_timeout; extern bool trace_sort; #endif +/* global temporary table */ +extern int max_active_gtt; +/* end */ + /* * Functions exported by guc.c */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 0b5957b..0720a4c 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -57,7 +57,7 @@ typedef struct RelationData struct SMgrRelationData *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 @@ -305,6 +305,7 @@ typedef struct StdRdOptions int parallel_workers; /* max number of parallel workers */ bool vacuum_index_cleanup; /* enables index vacuuming and cleanup */ bool vacuum_truncate; /* enables vacuum to truncate a relation */ + bool on_commit_delete_rows; /* global temp table */ } StdRdOptions; #define HEAP_MIN_FILLFACTOR 10 @@ -570,11 +571,13 @@ typedef struct ViewOptions * 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. * @@ -582,6 +585,7 @@ typedef struct ViewOptions */ #define RELATION_IS_LOCAL(relation) \ ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \ (relation)->rd_createSubid != InvalidSubTransactionId) /* @@ -594,6 +598,14 @@ typedef struct ViewOptions ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ !(relation)->rd_islocaltemp) +/* + * RELATION_IS_TEMP + * Test a rel is either local temp relation of this session + * or global temp relation. + */ +#define RELATION_IS_TEMP(relation) \ + ((relation)->rd_islocaltemp || \ + (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) /* * RelationIsScannable @@ -637,6 +649,17 @@ typedef struct ViewOptions RelationNeedsWAL(relation) && \ !IsCatalogRelation(relation)) +/* global temp table implementations */ +#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + +#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) + +#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence) + /* routines in utils/cache/relcache.c */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel); diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out new file mode 100644 index 0000000..ca2d135 --- /dev/null +++ b/src/test/regress/expected/gtt_clean.out @@ -0,0 +1,14 @@ +reset search_path; +select pg_sleep(5); + pg_sleep +---------- + +(1 row) + +drop schema gtt cascade; +NOTICE: drop cascades to 5 other objects +DETAIL: drop cascades to table gtt.gtt1 +drop cascades to table gtt.gtt2 +drop cascades to table gtt.gtt3 +drop cascades to table gtt.gtt_t_kenyon +drop cascades to table gtt.gtt_with_seq diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out new file mode 100644 index 0000000..7365ffe --- /dev/null +++ b/src/test/regress/expected/gtt_function.out @@ -0,0 +1,383 @@ +CREATE SCHEMA IF NOT EXISTS gtt_function; +set search_path=gtt_function,sys; +create global temp table gtt1(a int primary key, b text); +create global temp table gtt_test_rename(a int primary key, b text); +create global temp table gtt2(a int primary key, b text) on commit delete rows; +create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows; +create global temp table tmp_t0(c0 tsvector,c1 varchar(100)); +create table tbl_inherits_parent( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +); +create global temp table tbl_inherits_parent_global_temp( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +)on commit delete rows; +CREATE global temp TABLE products ( + product_no integer PRIMARY KEY, + name text, + price numeric +); +create global temp table gtt6(n int) with (on_commit_delete_rows='true'); +begin; +insert into gtt6 values (9); +-- 1 row +select * from gtt6; + n +--- + 9 +(1 row) + +commit; +-- 0 row +select * from gtt6; + n +--- +(0 rows) + +-- ERROR +create index CONCURRENTLY idx_gtt1 on gtt1 (b); +-- ERROR +cluster gtt1 using gtt1_pkey; +-- ERROR +create table gtt1(a int primary key, b text) on commit delete rows; +ERROR: ON COMMIT can only be used on temporary tables +-- ERROR +alter table gtt1 SET TABLESPACE pg_default; +ERROR: not support alter table set tablespace on global temp table +-- ERROR +alter table gtt1 set ( on_commit_delete_rows='true'); +ERROR: table cannot add or modify on commit parameter by ALTER TABLE command. +-- ERROR +create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true); +ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table +-- ERROR +create or replace global temp view gtt_v as select 5; +ERROR: views cannot be global temp because they do not have storage +create table foo(); +-- ERROR +alter table foo set (on_commit_delete_rows='true'); +ERROR: table cannot add or modify on commit parameter by ALTER TABLE command. +-- ok +CREATE global temp TABLE measurement ( + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); +--ok +CREATE global temp TABLE p_table01 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +)on commit delete rows; + +CREATE global temp TABLE p_table01_2018 +PARTITION OF p_table01 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows; + +CREATE global temp TABLE p_table01_2017 +PARTITION OF p_table01 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows; +begin; +insert into p_table01 values(1,'2018-01-02 00:00:00','test1'); +insert into p_table01 values(1,'2018-01-02 00:00:00','test2'); +select count(*) from p_table01; + count +------- + 2 +(1 row) + +commit; +select count(*) from p_table01; + count +------- + 0 +(1 row) + +--ok +CREATE global temp TABLE p_table02 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +) +on commit PRESERVE rows; +CREATE global temp TABLE p_table02_2018 +PARTITION OF p_table02 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00'); +CREATE global temp TABLE p_table02_2017 +PARTITION OF p_table02 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00'); +-- ERROR +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); +ERROR: The parent table must be global temporary table +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows; +select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname; + relname | relkind | relpersistence | reloptions +---------------------------------+---------+----------------+------------------------------- + p_table01 | p | g | {on_commit_delete_rows=true} + p_table01_2017 | r | g | {on_commit_delete_rows=true} + p_table01_2018 | r | g | {on_commit_delete_rows=true} + p_table01_id_seq | S | g | + p_table02 | p | g | {on_commit_delete_rows=false} + p_table02_2017 | r | g | {on_commit_delete_rows=false} + p_table02_2018 | r | g | {on_commit_delete_rows=false} + p_table02_id_seq | S | g | + tbl_inherits_parent | r | p | + tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true} + tbl_inherits_partition | r | g | {on_commit_delete_rows=true} +(11 rows) + +-- ERROR +create global temp table gtt3(a int primary key, b text) on commit drop; +ERROR: global temp table not support on commit drop clause +-- ERROR +create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows; +ERROR: could not create global temporary table with on commit and with clause at same time +-- ok +create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true); +--ok +alter table gtt_test_rename rename to gtt_test_new; +-- ok +ALTER TABLE gtt_test_new ADD COLUMN address varchar(30); +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); +ERROR: constraints on permanent tables may reference only permanent tables +-- ok +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +)on commit delete rows; +--ERROR +insert into orders values(1,1,1); +ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey" +DETAIL: Key (product_no)=(1) is not present in table "products". +--ok +insert into products values(1,'test',1.0); +begin; +insert into orders values(1,1,1); +commit; +select count(*) from products; + count +------- + 1 +(1 row) + +select count(*) from orders; + count +------- + 0 +(1 row) + +-- ok +CREATE GLOBAL TEMPORARY TABLE mytable ( + id SERIAL PRIMARY KEY, + data text +) on commit preserve rows; +-- ok +create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows; +insert into gtt_seq (a) values(1); +insert into gtt_seq (a) values(2); +select * from gtt_seq order by id; + id | a +----+--- + 2 | 1 + 3 | 2 +(2 rows) + +truncate gtt_seq; +select * from gtt_seq order by id; + id | a +----+--- +(0 rows) + +insert into gtt_seq (a) values(3); +select * from gtt_seq order by id; + id | a +----+--- + 4 | 3 +(1 row) + +--ERROR +CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1; +ERROR: materialized views must not use global temporary tables or views +-- ok +create index idx_gtt1_1 on gtt1 using hash (a); +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +create index idx_tmp_t0_2 on tmp_t0 using gist (c0); +--ok +create global temp table gt (a SERIAL,b int); +begin; +set transaction_read_only = true; +insert into gt (b) values(1); +select * from gt; + a | b +---+--- + 1 | 1 +(1 row) + +commit; +create sequence seq_1; +CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS; +alter table gtt_s_1 add c2 int default nextval('seq_1'); +alter table gtt_s_2 add c2 int default nextval('seq_1'); +begin; +insert into gtt_s_1 (c1)values(1); +insert into gtt_s_2 (c1)values(1); +insert into gtt_s_1 (c1)values(2); +insert into gtt_s_2 (c1)values(2); +select * from gtt_s_1 order by c1; + c1 | c2 +----+---- + 1 | 1 + 2 | 3 +(2 rows) + +commit; +select * from gtt_s_1 order by c1; + c1 | c2 +----+---- +(0 rows) + +select * from gtt_s_2 order by c1; + c1 | c2 +----+---- + 1 | 2 + 2 | 4 +(2 rows) + +--ok +create global temp table gt1(a int); +insert into gt1 values(generate_series(1,100000)); +create index idx_gt1_1 on gt1 (a); +create index idx_gt1_2 on gt1((a + 1)); +create index idx_gt1_3 on gt1((a*10),(a+a),(a-1)); +explain (costs off) select * from gt1 where a=1; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: (a = 1) + -> Bitmap Index Scan on idx_gt1_1 + Index Cond: (a = 1) +(4 rows) + +explain (costs off) select * from gt1 where a=200000; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: (a = 200000) + -> Bitmap Index Scan on idx_gt1_1 + Index Cond: (a = 200000) +(4 rows) + +explain (costs off) select * from gt1 where a*10=300; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: ((a * 10) = 300) + -> Bitmap Index Scan on idx_gt1_3 + Index Cond: ((a * 10) = 300) +(4 rows) + +explain (costs off) select * from gt1 where a*10=3; + QUERY PLAN +-------------------------------------- + Bitmap Heap Scan on gt1 + Recheck Cond: ((a * 10) = 3) + -> Bitmap Index Scan on idx_gt1_3 + Index Cond: ((a * 10) = 3) +(4 rows) + +analyze gt1; +explain (costs off) select * from gt1 where a=1; + QUERY PLAN +---------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 + Index Cond: (a = 1) +(2 rows) + +explain (costs off) select * from gt1 where a=200000; + QUERY PLAN +---------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 + Index Cond: (a = 200000) +(2 rows) + +explain (costs off) select * from gt1 where a*10=300; + QUERY PLAN +----------------------------------- + Index Scan using idx_gt1_3 on gt1 + Index Cond: ((a * 10) = 300) +(2 rows) + +explain (costs off) select * from gt1 where a*10=3; + QUERY PLAN +----------------------------------- + Index Scan using idx_gt1_3 on gt1 + Index Cond: ((a * 10) = 3) +(2 rows) + +--ok +create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1'); +create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0'); +create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t'); +create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f'); +create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes'); +create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no'); +create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y'); +create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n'); +--error +create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr'); +ERROR: parameter "on_commit_delete_rows" requires a Boolean value +create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye'); +ERROR: parameter "on_commit_delete_rows" requires a Boolean value +reset search_path; +drop schema gtt_function cascade; +NOTICE: drop cascades to 31 other objects +DETAIL: drop cascades to table gtt_function.gtt1 +drop cascades to table gtt_function.gtt_test_new +drop cascades to table gtt_function.gtt2 +drop cascades to table gtt_function.gtt3 +drop cascades to table gtt_function.tmp_t0 +drop cascades to table gtt_function.tbl_inherits_parent +drop cascades to table gtt_function.tbl_inherits_parent_global_temp +drop cascades to table gtt_function.products +drop cascades to table gtt_function.gtt6 +drop cascades to table gtt_function.foo +drop cascades to table gtt_function.measurement +drop cascades to table gtt_function.p_table01 +drop cascades to table gtt_function.p_table02 +drop cascades to table gtt_function.tbl_inherits_partition +drop cascades to table gtt_function.gtt5 +drop cascades to table gtt_function.orders +drop cascades to table gtt_function.mytable +drop cascades to table gtt_function.gtt_seq +drop cascades to table gtt_function.gt +drop cascades to sequence gtt_function.seq_1 +drop cascades to table gtt_function.gtt_s_1 +drop cascades to table gtt_function.gtt_s_2 +drop cascades to table gtt_function.gt1 +drop cascades to table gtt_function.gtt_test1 +drop cascades to table gtt_function.gtt_test2 +drop cascades to table gtt_function.gtt_test3 +drop cascades to table gtt_function.gtt_test4 +drop cascades to table gtt_function.gtt_test5 +drop cascades to table gtt_function.gtt_test6 +drop cascades to table gtt_function.gtt_test7 +drop cascades to table gtt_function.gtt_test8 diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out new file mode 100644 index 0000000..0646aae --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_1.out @@ -0,0 +1,90 @@ +set search_path=gtt,sys; +select nextval('gtt_with_seq_c2_seq'); + nextval +--------- + 1 +(1 row) + +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +commit; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +rollback; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +truncate gtt1; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt1; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +insert into gtt1 values(1, 'test1'); +rollback; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +begin; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +truncate gtt1; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt1; +commit; +select * from gtt1 order by a; + a | b +---+--- +(0 rows) + +reset search_path; diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out new file mode 100644 index 0000000..2f084be --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_2.out @@ -0,0 +1,343 @@ +set search_path=gtt,sys; +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +insert into gtt3 values(2, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +commit; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +begin; +insert into gtt3 values(3, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 + 3 | test1 +(3 rows) + +rollback; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test1 +(2 rows) + +truncate gtt3; +select * from gtt3 order by a; + a | b +---+--- +(0 rows) + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +insert into gtt3 values(2, 'test2'); +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 + 2 | test2 +(2 rows) + +truncate gtt3; +select * from gtt3 order by a; + a | b +---+--- +(0 rows) + +insert into gtt3 values(3, 'test3'); +update gtt3 set a = 3 where b = 'test1'; +select * from gtt3 order by a; + a | b +---+------- + 3 | test3 +(1 row) + +rollback; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +begin; +select * from gtt3 order by a; + a | b +---+------- + 1 | test1 +(1 row) + +truncate gtt3; +insert into gtt3 values(5, 'test5'); +select * from gtt3 order by a; + a | b +---+------- + 5 | test5 +(1 row) + +truncate gtt3; +insert into gtt3 values(6, 'test6'); +commit; +select * from gtt3 order by a; + a | b +---+------- + 6 | test6 +(1 row) + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +begin; +insert into gtt3 values(2); +select * from gtt3; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; + a | b +---+--- + 4 | +(1 row) + +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +insert into gtt3 values(5); +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +rollback; +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; + a | b +---+--- + 1 | +(1 row) + +begin; +insert into gtt3 values(2); +select * from gtt3; + a | b +---+--- + 1 | + 2 | +(2 rows) + +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; + a | b +---+--- + 4 | +(1 row) + +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; + a | b +---+--- + 3 | +(1 row) + +insert into gtt3 values(5); +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +commit; +select * from gtt3; + a | b +---+--- + 3 | + 5 | +(2 rows) + +truncate gtt3; +insert into gtt3 values(generate_series(1,100000), 'testing'); +select count(*) from gtt3; + count +-------- + 100000 +(1 row) + +analyze gtt3; +explain (COSTS FALSE) select * from gtt3 where a =300; + QUERY PLAN +------------------------------------ + Index Scan using gtt3_pkey on gtt3 + Index Cond: (a = 300) +(2 rows) + +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon'; + relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size +--------------+------------------+------------------+---------------+-----------------+------------------------ + gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400 +(1 row) + +select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0; + relname +-------------- + gtt_t_kenyon +(1 row) + +select +c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid) +from +pg_class c +where +c.oid in +( +select +i.indexrelid as indexrelid +from +pg_index i ,pg_class cc +where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid +) +order by c.relname; + relname | pg_relation_size | pg_table_size | pg_total_relation_size +--------------------+------------------+---------------+------------------------ + idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536 + idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152 +(2 rows) + +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +vacuum full gtt_t_kenyon; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + count +------- + 2000 +(1 row) + +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + count +------- + 2006 +(1 row) + +insert into gtt_with_seq (c1) values(1); +select * from gtt_with_seq; + c1 | c2 +----+---- + 1 | 1 +(1 row) + +reset search_path; diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out new file mode 100644 index 0000000..8c0c376 --- /dev/null +++ b/src/test/regress/expected/gtt_prepare.out @@ -0,0 +1,10 @@ +CREATE SCHEMA IF NOT EXISTS gtt; +set search_path=gtt,sys; +create global temp table gtt1(a int primary key, b text) on commit delete rows; +create global temp table gtt2(a int primary key, b text) on commit delete rows; +create global temp table gtt3(a int primary key, b text); +create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows; +create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id); +create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark); +CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows; +reset search_path; diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out new file mode 100644 index 0000000..4420fdb --- /dev/null +++ b/src/test/regress/expected/gtt_stats.out @@ -0,0 +1,80 @@ +CREATE SCHEMA IF NOT EXISTS gtt_stats; +set search_path=gtt_stats,sys; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + count +------- + 0 +(1 row) + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 0 +(1 row) + +create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + count +------- + 1 +(1 row) + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 2 +(1 row) + +insert into gtt values(generate_series(1,10000),'test'); +-- expect 1 +select count(*) from pg_gtt_attached_pids; + count +------- + 1 +(1 row) + +-- expect 2 +select count(*) from pg_list_gtt_relfrozenxids(); + count +------- + 2 +(1 row) + +-- expect 2 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 0 | 0 | 0 + gtt_stats | gtt_pkey | 1 | 0 | 0 +(2 rows) + +-- expect 0 +select * from pg_gtt_stats order by tablename; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram +------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+---------------------- +(0 rows) + +reindex table gtt; +reindex index gtt_pkey; +analyze gtt; +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 55 | 10000 | 0 + gtt_stats | gtt_pkey | 30 | 10000 | 0 +(2 rows) + +select * from pg_gtt_stats order by tablename; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram +------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+---------------------- + gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | | + gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | | +(2 rows) + +reset search_path; +drop schema gtt_stats cascade; +NOTICE: drop cascades to table gtt_stats.gtt diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 6eec8ec..8ea1cc1 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1349,6 +1349,94 @@ pg_group| SELECT pg_authid.rolname AS groname, WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist FROM pg_authid WHERE (NOT pg_authid.rolcanlogin); +pg_gtt_attached_pids| SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.relid, + s.pid + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), + LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_gtt_relstats| SELECT n.nspname AS schemaname, + c.relname AS tablename, + s.relfilenode, + s.relpages, + s.reltuples, + s.relallvisible, + s.relfrozenxid, + s.relminmxid + FROM (pg_class c + LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))), + LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid)))); +pg_gtt_stats| 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 + ELSE NULL::text[] + 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 + ELSE NULL::real[] + 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 + ELSE NULL::text[] + 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] + ELSE NULL::real + 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 + ELSE NULL::text[] + 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 + ELSE NULL::real[] + 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 + ELSE NULL::real[] + 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))), + LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(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) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) 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)))); pg_hba_file_rules| SELECT a.line_number, a.type, a.database, diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 95f1925..76b2374 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -121,3 +121,10 @@ test: fast_default # run stats by itself because its delay may be insufficient under heavy load test: stats + +# global temp table test +test: gtt_stats +test: gtt_function +test: gtt_prepare +test: gtt_parallel_1 gtt_parallel_2 +test: gtt_clean diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql new file mode 100644 index 0000000..2c8e586 --- /dev/null +++ b/src/test/regress/sql/gtt_clean.sql @@ -0,0 +1,8 @@ + + +reset search_path; + +select pg_sleep(5); + +drop schema gtt cascade; + diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql new file mode 100644 index 0000000..81f0bfc --- /dev/null +++ b/src/test/regress/sql/gtt_function.sql @@ -0,0 +1,253 @@ + +CREATE SCHEMA IF NOT EXISTS gtt_function; + +set search_path=gtt_function,sys; + +create global temp table gtt1(a int primary key, b text); + +create global temp table gtt_test_rename(a int primary key, b text); + +create global temp table gtt2(a int primary key, b text) on commit delete rows; + +create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows; + +create global temp table tmp_t0(c0 tsvector,c1 varchar(100)); + +create table tbl_inherits_parent( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +); + +create global temp table tbl_inherits_parent_global_temp( +a int not null, +b varchar(32) not null default 'Got u', +c int check (c > 0), +d date not null +)on commit delete rows; + +CREATE global temp TABLE products ( + product_no integer PRIMARY KEY, + name text, + price numeric +); + +create global temp table gtt6(n int) with (on_commit_delete_rows='true'); + +begin; +insert into gtt6 values (9); +-- 1 row +select * from gtt6; +commit; +-- 0 row +select * from gtt6; + +-- ERROR +create index CONCURRENTLY idx_gtt1 on gtt1 (b); + +-- ERROR +cluster gtt1 using gtt1_pkey; + +-- ERROR +create table gtt1(a int primary key, b text) on commit delete rows; + +-- ERROR +alter table gtt1 SET TABLESPACE pg_default; + +-- ERROR +alter table gtt1 set ( on_commit_delete_rows='true'); + +-- ERROR +create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true); + +-- ERROR +create or replace global temp view gtt_v as select 5; + +create table foo(); +-- ERROR +alter table foo set (on_commit_delete_rows='true'); + +-- ok +CREATE global temp TABLE measurement ( + logdate date not null, + peaktemp int, + unitsales int +) PARTITION BY RANGE (logdate); + +--ok +CREATE global temp TABLE p_table01 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +)on commit delete rows; + +CREATE global temp TABLE p_table01_2018 +PARTITION OF p_table01 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows; + +CREATE global temp TABLE p_table01_2017 +PARTITION OF p_table01 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows; + +begin; +insert into p_table01 values(1,'2018-01-02 00:00:00','test1'); +insert into p_table01 values(1,'2018-01-02 00:00:00','test2'); +select count(*) from p_table01; +commit; + +select count(*) from p_table01; + +--ok +CREATE global temp TABLE p_table02 ( +id bigserial NOT NULL, +cre_time timestamp without time zone, +note varchar(30) +) PARTITION BY RANGE (cre_time) +WITH ( +OIDS = FALSE +) +on commit PRESERVE rows; + +CREATE global temp TABLE p_table02_2018 +PARTITION OF p_table02 +FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00'); + +CREATE global temp TABLE p_table02_2017 +PARTITION OF p_table02 +FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00'); + +-- ERROR +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); + +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows; + +select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname; + +-- ERROR +create global temp table gtt3(a int primary key, b text) on commit drop; + +-- ERROR +create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows; + +-- ok +create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true); + +--ok +alter table gtt_test_rename rename to gtt_test_new; + +-- ok +ALTER TABLE gtt_test_new ADD COLUMN address varchar(30); + +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); + +-- ok +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +)on commit delete rows; + +--ERROR +insert into orders values(1,1,1); + +--ok +insert into products values(1,'test',1.0); + +begin; +insert into orders values(1,1,1); +commit; + +select count(*) from products; +select count(*) from orders; + +-- ok +CREATE GLOBAL TEMPORARY TABLE mytable ( + id SERIAL PRIMARY KEY, + data text +) on commit preserve rows; + +-- ok +create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows; +insert into gtt_seq (a) values(1); +insert into gtt_seq (a) values(2); +select * from gtt_seq order by id; +truncate gtt_seq; +select * from gtt_seq order by id; +insert into gtt_seq (a) values(3); +select * from gtt_seq order by id; + +--ERROR +CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1; + +-- ok +create index idx_gtt1_1 on gtt1 using hash (a); +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +create index idx_tmp_t0_2 on tmp_t0 using gist (c0); + +--ok +create global temp table gt (a SERIAL,b int); +begin; +set transaction_read_only = true; +insert into gt (b) values(1); +select * from gt; +commit; + +create sequence seq_1; +CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS; +CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS; +alter table gtt_s_1 add c2 int default nextval('seq_1'); +alter table gtt_s_2 add c2 int default nextval('seq_1'); +begin; +insert into gtt_s_1 (c1)values(1); +insert into gtt_s_2 (c1)values(1); +insert into gtt_s_1 (c1)values(2); +insert into gtt_s_2 (c1)values(2); +select * from gtt_s_1 order by c1; +commit; +select * from gtt_s_1 order by c1; +select * from gtt_s_2 order by c1; + +--ok +create global temp table gt1(a int); +insert into gt1 values(generate_series(1,100000)); +create index idx_gt1_1 on gt1 (a); +create index idx_gt1_2 on gt1((a + 1)); +create index idx_gt1_3 on gt1((a*10),(a+a),(a-1)); +explain (costs off) select * from gt1 where a=1; +explain (costs off) select * from gt1 where a=200000; +explain (costs off) select * from gt1 where a*10=300; +explain (costs off) select * from gt1 where a*10=3; +analyze gt1; +explain (costs off) select * from gt1 where a=1; +explain (costs off) select * from gt1 where a=200000; +explain (costs off) select * from gt1 where a*10=300; +explain (costs off) select * from gt1 where a*10=3; + +--ok +create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1'); +create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0'); +create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t'); +create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f'); +create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes'); +create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no'); +create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y'); +create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n'); + +--error +create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr'); +create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye'); + +reset search_path; + +drop schema gtt_function cascade; + diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql new file mode 100644 index 0000000..d05745e --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_1.sql @@ -0,0 +1,44 @@ + + +set search_path=gtt,sys; + +select nextval('gtt_with_seq_c2_seq'); + +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +commit; +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +rollback; +select * from gtt1 order by a; + +truncate gtt1; +select * from gtt1 order by a; + +begin; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +truncate gtt1; +select * from gtt1 order by a; +insert into gtt1 values(1, 'test1'); +rollback; +select * from gtt1 order by a; + +begin; +select * from gtt1 order by a; +truncate gtt1; +insert into gtt1 values(1, 'test1'); +select * from gtt1 order by a; +truncate gtt1; +commit; +select * from gtt1 order by a; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql new file mode 100644 index 0000000..39cca5e --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_2.sql @@ -0,0 +1,154 @@ + + +set search_path=gtt,sys; + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + +begin; +insert into gtt3 values(2, 'test1'); +select * from gtt3 order by a; +commit; +select * from gtt3 order by a; + +begin; +insert into gtt3 values(3, 'test1'); +select * from gtt3 order by a; +rollback; +select * from gtt3 order by a; + +truncate gtt3; +select * from gtt3 order by a; + +insert into gtt3 values(1, 'test1'); +select * from gtt3 order by a; + +begin; +insert into gtt3 values(2, 'test2'); +select * from gtt3 order by a; +truncate gtt3; +select * from gtt3 order by a; +insert into gtt3 values(3, 'test3'); +update gtt3 set a = 3 where b = 'test1'; +select * from gtt3 order by a; +rollback; +select * from gtt3 order by a; + +begin; +select * from gtt3 order by a; +truncate gtt3; +insert into gtt3 values(5, 'test5'); +select * from gtt3 order by a; +truncate gtt3; +insert into gtt3 values(6, 'test6'); +commit; +select * from gtt3 order by a; + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; +begin; +insert into gtt3 values(2); +select * from gtt3; +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; +insert into gtt3 values(5); +select * from gtt3; +rollback; +select * from gtt3; + +truncate gtt3; +insert into gtt3 values(1); +select * from gtt3; +begin; +insert into gtt3 values(2); +select * from gtt3; +SAVEPOINT save1; +truncate gtt3; +insert into gtt3 values(3); +select * from gtt3; +SAVEPOINT save2; +truncate gtt3; +insert into gtt3 values(4); +select * from gtt3; +SAVEPOINT save3; +rollback to savepoint save2; +Select * from gtt3; +insert into gtt3 values(5); +select * from gtt3; +commit; +select * from gtt3; + +truncate gtt3; +insert into gtt3 values(generate_series(1,100000), 'testing'); +select count(*) from gtt3; +analyze gtt3; +explain (COSTS FALSE) select * from gtt3 where a =300; + +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon'; +select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0; + +select +c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid) +from +pg_class c +where +c.oid in +( +select +i.indexrelid as indexrelid +from +pg_index i ,pg_class cc +where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid +) +order by c.relname; + +select count(*) from gtt_t_kenyon; +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + +begin; +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + +vacuum full gtt_t_kenyon; +select count(*) from gtt_t_kenyon; + +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +commit; +select count(*) from gtt_t_kenyon; + +insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000); +insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000); +insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500); +begin; +truncate gtt_t_kenyon; +insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500); +cluster gtt_t_kenyon using idx_gtt_t_kenyon_1; +rollback; +select count(*) from gtt_t_kenyon; + +insert into gtt_with_seq (c1) values(1); +select * from gtt_with_seq; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql new file mode 100644 index 0000000..dbe84d1 --- /dev/null +++ b/src/test/regress/sql/gtt_prepare.sql @@ -0,0 +1,19 @@ + +CREATE SCHEMA IF NOT EXISTS gtt; + +set search_path=gtt,sys; + +create global temp table gtt1(a int primary key, b text) on commit delete rows; + +create global temp table gtt2(a int primary key, b text) on commit delete rows; + +create global temp table gtt3(a int primary key, b text); + +create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows; +create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id); +create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark); + +CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows; + +reset search_path; + diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql new file mode 100644 index 0000000..d61b0ff --- /dev/null +++ b/src/test/regress/sql/gtt_stats.sql @@ -0,0 +1,46 @@ + +CREATE SCHEMA IF NOT EXISTS gtt_stats; + +set search_path=gtt_stats,sys; + +-- expect 0 +select count(*) from pg_gtt_attached_pids; + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + +create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows; +-- expect 0 +select count(*) from pg_gtt_attached_pids; + +-- expect 0 +select count(*) from pg_list_gtt_relfrozenxids(); + +insert into gtt values(generate_series(1,10000),'test'); + +-- expect 1 +select count(*) from pg_gtt_attached_pids; + +-- expect 2 +select count(*) from pg_list_gtt_relfrozenxids(); + +-- expect 2 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + +-- expect 0 +select * from pg_gtt_stats order by tablename; + +reindex table gtt; + +reindex index gtt_pkey; + +analyze gtt; + +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename; + +select * from pg_gtt_stats order by tablename; + +reset search_path; + +drop schema gtt_stats cascade; +