diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c index 79430d2..babb5f3 100644 --- a/src/backend/access/common/reloptions.c +++ b/src/backend/access/common/reloptions.c @@ -158,6 +158,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}} }; @@ -1486,6 +1499,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)}, @@ -1586,13 +1601,18 @@ build_reloptions(Datum reloptions, 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 dd975b1..1610e7d 100644 --- a/src/backend/access/gist/gistutil.c +++ b/src/backend/access/gist/gistutil.c @@ -1013,7 +1013,9 @@ gistGetFakeLSN(Relation rel) { static XLogRecPtr counter = FirstNormalUnloggedLSN; - 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 4871b7f..16b00c9 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -149,7 +149,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 3fa4b76..b54882d 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -598,7 +598,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 @@ -651,7 +651,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 8ce5011..0d6ae76 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" @@ -399,9 +400,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 f05cbe7..946c9d2 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access) /* Read an existing block of the relation */ buf = ReadBuffer(rel, blkno); LockBuffer(buf, access); - _bt_checkpage(rel, buf); + + /* global temp table may be not yet initialized for this backend. */ + if (RELATION_IS_GLOBAL_TEMP(rel) && + blkno == BTREE_METAPAGE && + PageIsNew(BufferGetPage(buf))) + _bt_initmetapage(BufferGetPage(buf), P_NONE, 0); + else + _bt_checkpage(rel, buf); } else { diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 3813ead..fd731d8 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -6325,6 +6325,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 f8f0b48..dc8bbb1 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -42,6 +42,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 0fdff29..210d019 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, @@ -404,6 +406,10 @@ heap_create(const char *relname, relpersistence, relkind); + /* global temp table not create storage file when catalog create */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + create_storage = false; + /* * Have the storage manager create the relation's disk file, if needed. * @@ -427,7 +433,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: @@ -956,6 +962,7 @@ AddNewRelationTuple(Relation pg_class_desc, Oid reloftype, Oid relowner, char relkind, + char relpersistence, TransactionId relfrozenxid, TransactionId relminmxid, Datum relacl, @@ -994,8 +1001,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; @@ -1357,6 +1374,7 @@ heap_create_with_catalog(const char *relname, reloftypeid, ownerid, relkind, + relpersistence, relfrozenxid, relminmxid, PointerGetDatum(relacl), @@ -1441,11 +1459,15 @@ heap_create_with_catalog(const char *relname, */ StoreConstraints(new_rel_desc, cooked_constraints, is_internal); - /* - * If there's a special on-commit action, remember it - */ - if (oncommit != ONCOMMIT_NOOP) - register_on_commit_action(relid, oncommit); + /* global temp table register action when storage init */ + if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP) + { + /* + * If there's a special on-commit action, remember it + */ + if (oncommit != ONCOMMIT_NOOP) + register_on_commit_action(relid, oncommit); + } /* * ok, the relation has been cataloged, so close our relations and return @@ -1937,6 +1959,13 @@ 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(rel->rd_node)) + elog(ERROR, "can not drop relation when other backend attached this global temp table"); + } + /* * Schedule unlinking of the relation's physical files at commit. */ @@ -3165,7 +3194,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; @@ -3176,8 +3205,12 @@ RelationTruncateIndexes(Relation heapRelation) Relation currentIndex; IndexInfo *indexInfo; + if (RELATION_IS_GLOBAL_TEMP(heapRelation) && + !gtt_storage_attached(indexId)) + continue; + /* 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 @@ -3223,8 +3256,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); } @@ -3257,6 +3295,8 @@ void heap_truncate_one_rel(Relation rel) { Oid toastrelid; + LOCKMODE lockmode = AccessExclusiveLock; + bool truncate_toastrel = false; /* * Truncate the relation. Partitioned tables have no storage, so there is @@ -3265,23 +3305,40 @@ heap_truncate_one_rel(Relation rel) if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) return; + toastrelid = rel->rd_rel->reltoastrelid; + + /* + * Truncate global temp table only need RowExclusiveLock + */ + if (RELATION_IS_GLOBAL_TEMP(rel)) + { + lockmode = RowExclusiveLock; + if (OidIsValid(toastrelid) && + gtt_storage_attached(toastrelid)) + truncate_toastrel = true; + } + else if (OidIsValid(toastrelid)) + truncate_toastrel = true; + /* 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)) + if (truncate_toastrel) { - 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 8880586..0852073 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -52,6 +52,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" @@ -877,6 +878,22 @@ index_create(Relation heapRelation, indexRelationName, RelationGetRelationName(heapRelation)))); } + if (RELATION_IS_GLOBAL_TEMP(heapRelation)) + { + if (accessMethodObjectId != BTREE_AM_OID) + elog(ERROR, "only support btree index on global temp table"); + + /* No support create index on global temp table use concurrent mode yet */ + if (concurrent) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot create indexes on global temporary tables using concurrent mode"))); + + /* if global temp table not init storage, then skip build index */ + if (!gtt_storage_attached(heapRelation->rd_node.relNode)) + flags |= INDEX_CREATE_SKIP_BUILD; + } + /* * construct tuple descriptor for index tuples */ @@ -2054,6 +2071,13 @@ 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(userHeapRelation->rd_node)) + elog(ERROR, "can not drop index when other backend attached this global temp table."); + } + /* * Drop Index Concurrently is more or less the reverse process of Create * Index Concurrently. @@ -2660,6 +2684,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, @@ -2745,21 +2774,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); + } } /* @@ -2873,6 +2916,15 @@ index_build(Relation heapRelation, pgstat_progress_update_multi_param(6, index, val); } + if (RELATION_IS_GLOBAL_TEMP(indexRelation)) + { + if (!gtt_storage_attached(indexRelation->rd_node.relNode)) + { + gtt_force_enable_index(indexRelation); + RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation); + } + } + /* * Call the access method's build procedure */ @@ -3468,6 +3520,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence, errmsg("cannot reindex temporary tables of other sessions"))); /* + * Because global temp table cannot change relfilenode + * no support reindex on global temp table yet. + */ + if (RELATION_IS_GLOBAL_TEMP(iRel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot reindex global temporary tables"))); + + /* * Also check for active uses of the index in the current transaction; we * don't want to reindex underneath an open indexscan. */ diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index e70243a..301da79 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -647,6 +647,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 fddfbf1..671c614 100644 --- a/src/backend/catalog/storage.c +++ b/src/backend/catalog/storage.c @@ -26,6 +26,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" @@ -75,7 +76,7 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */ * 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; @@ -85,6 +86,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; @@ -117,6 +120,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence) pending->next = pendingDeletes; pendingDeletes = pending; + /* remember global temp table storage info to localhash */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel) + remember_gtt_storage_info(rnode, rel); + return srel; } @@ -486,8 +493,15 @@ 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]) && + gtt_storage_attached(srels[i]->smgr_rnode.node.relNode)) + forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode); + } + pfree(srels); } } diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c new file mode 100644 index 0000000..71be416 --- /dev/null +++ b/src/backend/catalog/storage_gtt.c @@ -0,0 +1,1174 @@ +/*------------------------------------------------------------------------- + * + * 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/table.h" +#include "access/visibilitymap.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" +#include "access/htup_details.h" +#include "access/multixact.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/pg_type.h" +#include "catalog/pg_statistic.h" +#include "commands/tablecmds.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 +{ + RelFileNode rnode; + Bitmapset *map; + /* bitmap data */ +} gtt_shared_hash_entry; + +typedef struct +{ + Oid relid; + + Oid spcnode; + /* pg_class stat */ + int32 relpages; + float4 reltuples; + int32 relallvisible; + TransactionId relfrozenxid; + TransactionId relminmxid; + char relkind; + bool on_commit_delete; + + /* pg_statistic */ + int natts; + int *attnum; + HeapTuple *att_stat_tups; +} gtt_local_hash_entry; + +static Size action_gtt_shared_hash_entry_size(void); +static void gtt_storage_checkin(RelFileNode rnode); +static void gtt_storage_checkout(RelFileNode rnode, bool skiplock); +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); + +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(); + } + + info.keysize = sizeof(RelFileNode); + info.entrysize = gtt_shared_ctl->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(RelFileNode rnode) +{ + gtt_shared_hash_entry *entry; + bool found; + + if (max_active_gtt <= 0) + return; + + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash, + &rnode, 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_gtt."))); + } + + 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(RelFileNode rnode, bool skiplock) +{ + gtt_shared_hash_entry *entry; + + if (max_active_gtt <= 0) + return; + + if (!skiplock) + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + + entry = hash_search(active_gtt_shared_hash, + (void *) &(rnode), HASH_FIND, NULL); + + if (entry == NULL) + { + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget", + rnode.dbNode, rnode.spcNode, rnode.relNode); + 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, &rnode, HASH_REMOVE, NULL)) + elog(PANIC, "gtt shared hash table corrupted"); + } + + if (!skiplock) + LWLockRelease(>t_shared_ctl->lock); + + return; +} + +Bitmapset * +copy_active_gtt_bitmap(RelFileNode node) +{ + gtt_shared_hash_entry *entry; + Bitmapset *map_copy = NULL; + + if (max_active_gtt <= 0) + return NULL; + + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(node), 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(RelFileNode node) +{ + gtt_shared_hash_entry *entry; + bool in_use = false; + int num_use = 0; + + if (max_active_gtt <= 0) + return false; + + LWLockAcquire(>t_shared_ctl->lock, LW_SHARED); + entry = hash_search(active_gtt_shared_hash, + (void *) &(node), 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; + Oid relid = rnode.relNode; + bool found; + int natts = 0; + + if (max_active_gtt <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Global temporary table feature is disable"), + errhint("You might need to increase max_active_gtt to enable this feature."))); + + if (RecoveryInProgress()) + elog(ERROR, "readonly mode not support access global temp table yet"); + + if (rel->rd_rel->relkind == RELKIND_INDEX && + (!rel->rd_index->indisvalid || + !rel->rd_index->indisready || + !rel->rd_index->indislive)) + elog(ERROR, "invalid gtt index %s not allow to create stroage file", 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 temp relation table", + GTT_LOCAL_HASH_SIZE, + &ctl, HASH_ELEM | HASH_BLOBS); + + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + gtt_relstats_context = + AllocSetContextCreate(CacheMemoryContext, + "gtt relstats context", + ALLOCSET_DEFAULT_SIZES); + } + + /* Look up or create an entry */ + entry = hash_search(gtt_storage_local_hash, + (void *) &relid, HASH_ENTER, &found); + + if (found) + { + elog(ERROR, "backend %d relid %u already exists in global temp table local hash", + MyBackendId, relid); + } + + oldcontext = MemoryContextSwitchTo(gtt_relstats_context); + + entry->spcnode = rnode.spcNode; + entry->relpages = 0; + entry->reltuples = 0; + entry->relallvisible = 0; + entry->relkind = rel->rd_rel->relkind; + natts = RelationGetNumberOfAttributes(rel); + entry->natts = natts; + entry->attnum = palloc0(sizeof(int) * natts); + entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts); + /* only heap contain transaction information */ + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_PARTITIONED_TABLE) + { + if (RELATION_GTT_ON_COMMIT_DELETE(rel)) + { + entry->on_commit_delete = true; + register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS); + } + + entry->relfrozenxid = RecentXmin; + entry->relminmxid = GetOldestMultiXactId(); + insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid); + set_gtt_session_relfrozenxid(); + gtt_storage_checkin(rnode); + } + else + { + entry->on_commit_delete = false; + entry->relfrozenxid = 0; + entry->relminmxid = 0; + } + + 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) +{ + gtt_local_hash_entry *entry = NULL; + + if (max_active_gtt <= 0) + return; + + if (gtt_storage_local_hash == NULL) + return; + + entry = hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_FIND, NULL); + + if (entry) + { + int i; + + if (entry->relkind == RELKIND_RELATION || + entry->relkind == RELKIND_PARTITIONED_TABLE) + { + RelFileNode rnode; + + rnode.spcNode = entry->spcnode; + rnode.dbNode = MyDatabaseId; + rnode.relNode = entry->relid; + + gtt_storage_checkout(rnode, false); + + Assert(TransactionIdIsNormal(entry->relfrozenxid)); + remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + 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; + } + } + + pfree(entry->attnum); + pfree(entry->att_stat_tups); + } + + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_REMOVE, NULL); + + return; +} + +/* is the storage file was created in this backend */ +bool +gtt_storage_attached(Oid relid) +{ + bool found; + + if (max_active_gtt <= 0) + return false; + + if (gtt_storage_local_hash == NULL) + return false; + + hash_search(gtt_storage_local_hash, + (void *) &(relid), HASH_FIND, &found); + + return found; +} + +static void +gtt_storage_removeall(int code, Datum arg) +{ + HASH_SEQ_STATUS status; + gtt_local_hash_entry *entry; + int nrels = 0, + maxrels = 0; + SMgrRelation *srels = NULL; + RelFileNode *rnodes = NULL; + char *relkinds = NULL; + + 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) + { + SMgrRelation srel; + RelFileNode rnode; + + rnode.spcNode = entry->spcnode; + rnode.dbNode = MyDatabaseId; + rnode.relNode = entry->relid; + + srel = smgropen(rnode, MyBackendId); + + /* allocate the initial array, or extend it, if needed */ + if (maxrels == 0) + { + maxrels = 32; + srels = palloc(sizeof(SMgrRelation) * maxrels); + rnodes = palloc(sizeof(RelFileNode) * maxrels); + relkinds = palloc(sizeof(char) * maxrels); + } + else if (maxrels <= nrels) + { + maxrels *= 2; + srels = repalloc(srels, sizeof(SMgrRelation) * maxrels); + rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels); + relkinds = repalloc(relkinds, sizeof(char) * maxrels); + } + + srels[nrels] = srel; + rnodes[nrels] = rnode; + relkinds[nrels] = entry->relkind; + nrels++; + } + + if (nrels > 0) + { + int i; + + smgrdounlinkall(srels, nrels, false); + + LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE); + for (i = 0; i < nrels; i++) + { + smgrclose(srels[i]); + if (relkinds[i] == RELKIND_RELATION) + gtt_storage_checkout(rnodes[i], true); + } + LWLockRelease(>t_shared_ctl->lock); + + pfree(srels); + pfree(rnodes); + 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; + bool found; + + if (max_active_gtt <= 0) + return; + + if (gtt_storage_local_hash == NULL) + return; + + entry = hash_search(gtt_storage_local_hash, + (void *) &relid, HASH_FIND, &found); + + if (!found) + return; + + Assert(entry->spcnode); + + if (num_pages >= 0 && + entry->relpages != (int32)num_pages) + entry->relpages = (int32)num_pages; + + if (num_tuples >= 0 && + num_tuples != (float4)entry->reltuples) + entry->reltuples = (float4)num_tuples; + + /* only heap contain transaction information and relallvisible */ + if (entry->relkind == RELKIND_RELATION) + { + if (entry->relallvisible >= 0 && + entry->relallvisible != (int32)num_all_visible_pages) + { + entry->relallvisible = (int32)num_all_visible_pages; + } + + if (TransactionIdIsNormal(relfrozenxid) && + entry->relfrozenxid != relfrozenxid && + (TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) || + TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid))) + { + remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid); + entry->relfrozenxid = relfrozenxid; + insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid); + set_gtt_session_relfrozenxid(); + } + + if (MultiXactIdIsValid(relminmxid) && + entry->relminmxid != relminmxid && + (MultiXactIdPrecedes(entry->relminmxid, relminmxid) || + MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid))) + { + entry->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; + bool found; + + if (max_active_gtt <= 0) + return false; + + if (gtt_storage_local_hash == NULL) + return false; + + entry = hash_search(gtt_storage_local_hash, + (void *) &relid, HASH_FIND, &found); + + if (!found) + return false; + + Assert(entry->relid == relid); + + if (relpages) + *relpages = entry->relpages; + + if (reltuples) + *reltuples = entry->reltuples; + + if (relallvisible) + *relallvisible = entry->relallvisible; + + if (relfrozenxid) + *relfrozenxid = entry->relfrozenxid; + + if (relminmxid) + *relminmxid = entry->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; + bool found; + MemoryContext oldcontext; + int i = 0; + + if (max_active_gtt <= 0) + return; + + if (gtt_storage_local_hash == NULL) + return; + + entry = hash_search(gtt_storage_local_hash, + (void *) &reloid, HASH_FIND, &found); + + if (!found) + return; + + /* todo */ + 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; + bool found; + int i = 0; + + if (max_active_gtt <= 0) + return NULL; + + if (gtt_storage_local_hash == NULL) + return NULL; + + entry = hash_search(gtt_storage_local_hash, + (void *) &reloid, HASH_FIND, &found); + + if (!found) + 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 = table_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); + table_close(rel, NoLock); + return (Datum) 0; + } + + pg_tatistic = table_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); + } + + table_close(rel, NoLock); + table_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; + 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 = table_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); + table_close(rel, NoLock); + return (Datum) 0; + } + + if (get_gtt_relstats(reloid, + &relpages, &reltuples, &relallvisible, + &relfrozenxid, &relminmxid)) + { + Datum values[5]; + bool isnull[5]; + + memset(isnull, 0, sizeof(isnull)); + memset(values, 0, sizeof(values)); + values[0] = Int32GetDatum(relpages); + values[1] = Float4GetDatum((float4)reltuples); + values[2] = Int32GetDatum(relallvisible); + values[3] = UInt32GetDatum(relfrozenxid); + values[4] = UInt32GetDatum(relminmxid); + tuple = heap_form_tuple(tupdesc, values, isnull); + tuplestore_puttuple(tupstore, tuple); + } + + tuplestore_donestoring(tupstore); + table_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 = table_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); + table_close(rel, NoLock); + return (Datum) 0; + } + + map = copy_active_gtt_bitmap(rel->rd_node); + 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); + table_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; +} + diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c9e6060..d814ff2 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') 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','p') 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') 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 c4420dd..977a984 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); @@ -586,14 +587,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 +1458,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 +1560,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 e9d7a7f..3088e26 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -391,6 +391,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options) errmsg("cannot vacuum temporary tables of other sessions"))); } + /* not support cluster global temp table yet */ + if (RELATION_IS_GLOBAL_TEMP(OldHeap)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("not support cluster global temporary tables yet"))); + /* * Also check for active uses of the relation in the current transaction, * including open scans and pending AFTER trigger events. diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index ec20ba3..a29cc00 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -2614,6 +2614,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind, !isTempNamespace(classtuple->relnamespace)) continue; + /* not support reindex on global temp table, so skip it */ + if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) + { + ereport(WARNING, + (errmsg("global temp table \"%s.%s\" skip reindexed", + get_namespace_name(get_rel_namespace(relid)), + get_rel_name(relid)))); + continue; + } + /* Check user/system classification, and optionally skip */ if (objectKind == REINDEX_OBJECT_SYSTEM && !IsSystemClass(relid, classtuple)) diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c index 329ab84..dfa257c 100644 --- a/src/backend/commands/lockcmds.c +++ b/src/backend/commands/lockcmds.c @@ -96,7 +96,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid, * transaction. */ relpersistence = get_rel_persistence(relid); - if (relpersistence == RELPERSISTENCE_TEMP) + if (relpersistence == RELPERSISTENCE_TEMP || + relpersistence == RELPERSISTENCE_GLOBAL_TEMP) MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE; /* Check permissions. */ diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c index 6aab73b..44f350d 100644 --- a/src/backend/commands/sequence.c +++ b/src/backend/commands/sequence.c @@ -94,7 +94,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 +108,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 +223,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 +328,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 +341,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 +365,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 +416,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple) END_CRIT_SECTION(); - UnlockReleaseBuffer(buf); + if (lockBuffer) + UnlockReleaseBuffer(buf); } /* @@ -502,7 +509,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 */ @@ -1178,6 +1185,25 @@ read_seq_tuple(Relation rel, Buffer *buf, HeapTuple seqdatatuple) LockBuffer(*buf, BUFFER_LOCK_EXCLUSIVE); page = BufferGetPage(*buf); + if (GlobalTempRelationPageIsNotInitialized(rel, page)) + { + /* 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, *buf); + heap_freetuple(tuple); + } sm = (sequence_magic *) PageGetSpecialPointer(page); if (sm->magic != SEQ_MAGIC) @@ -1954,3 +1980,23 @@ 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; +} + diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 70589dd..81bd3e5 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" @@ -552,6 +553,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx, Relation partitionTbl); static List *GetParentedForeignKeyRefs(Relation partition); static void ATDetachCheckNoForeignKeyRefs(Relation partition); +static bool has_oncommit_option(List *options); /* ---------------------------------------------------------------- @@ -597,6 +599,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, LOCKMODE parentLockmode; const char *accessMethod = NULL; Oid accessMethodId = InvalidOid; + bool has_oncommit_clause = false; /* * Truncate relname to appropriate length (probably a waste of time, as @@ -607,8 +610,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"))); @@ -638,7 +643,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), @@ -739,6 +746,57 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, /* * Parse and validate reloptions, if any. */ + /* global temp table */ + has_oncommit_clause = has_oncommit_option(stmt->options); + if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && + (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE)) + { + /* inherit table or parition table inherit on commit property from parent table*/ + if (inheritOids && stmt->oncommit == ONCOMMIT_NOOP && !has_oncommit_clause) + { + 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"); + + if (RELATION_GTT_ON_COMMIT_DELETE(relation)) + stmt->oncommit = ONCOMMIT_DELETE_ROWS; + else + stmt->oncommit = ONCOMMIT_PRESERVE_ROWS; + table_close(relation, NoLock); + } + + if (has_oncommit_clause) + { + if (stmt->oncommit != ONCOMMIT_NOOP) + elog(ERROR, "can not defeine global temp table with on commit and with clause at same time"); + } + 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 (has_oncommit_clause) + elog(ERROR, "regular table cannot specifie on_commit_delete_rows"); + reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps, true, false); @@ -1816,7 +1874,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged, * table or the current physical file to be thrown away anyway. */ if (rel->rd_createSubid == mySubid || - rel->rd_newRelfilenodeSubid == mySubid) + rel->rd_newRelfilenodeSubid == mySubid || + RELATION_IS_GLOBAL_TEMP(rel)) { /* Immediate, non-rollbackable truncation is OK */ heap_truncate_one_rel(rel); @@ -3375,6 +3434,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo * specially. */ targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock); + + if (RELATION_IS_GLOBAL_TEMP(targetrelation)) + { + if (is_other_backend_use_gtt(targetrelation->rd_node)) + elog(ERROR, "can not rename relation when other backend attached this global temp table"); + } + namespaceId = RelationGetNamespace(targetrelation); /* @@ -3550,6 +3616,13 @@ 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(rel->rd_node)) + elog(ERROR, "can not alter relation when other backend attached this global temp table"); + } + CheckTableNotInUse(rel, "ALTER TABLE"); ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context); @@ -8123,6 +8196,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel, errmsg("referenced relation \"%s\" is not a table", RelationGetRelationName(pkrel)))); + /* global temp table not support foreign key constraint yet */ + if (RELATION_IS_GLOBAL_TEMP(pkrel)) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("referenced relation \"%s\" is not a global temp table", + RelationGetRelationName(pkrel)))); + if (!allowSystemTableMods && IsSystemRelation(pkrel)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), @@ -8162,6 +8242,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; + /* global temp table not support foreign key constraint yet */ + case RELPERSISTENCE_GLOBAL_TEMP: + ereport(ERROR, + (errcode(ERRCODE_INVALID_TABLE_DEFINITION), + errmsg("not support foreign key constraints on global temp table yet"))); + break; } /* @@ -13199,7 +13285,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, @@ -14606,7 +14692,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", @@ -17246,3 +17334,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition) table_close(rel, NoLock); } } + +static bool +has_oncommit_option(List *options) +{ + ListCell *listptr; + + foreach(listptr, options) + { + DefElem *def = (DefElem *) lfirst(listptr); + + if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0) + return true; + } + + return false; +} + diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d625d17..69ad24f 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); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index ee5c3a6..0d7a2d4 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(CreateCommandTag((Node *) plannedstmt)); } diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c index 8286d9c..c3992a4 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 d6f2153..310a9e2 100644 --- a/src/backend/optimizer/plan/planner.c +++ b/src/backend/optimizer/plan/planner.c @@ -6312,7 +6312,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/parser/analyze.c b/src/backend/parser/analyze.c index 748bebf..a5ddbcd 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -2579,6 +2579,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 1b0edf5..492639f 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -3290,17 +3290,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; } @@ -11591,19 +11585,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 ee2d2b5..9c9abaa 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 diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6d1f28c..ed837d1 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2086,6 +2086,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); @@ -2152,7 +2157,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 aba3960..3c4b96c 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" @@ -52,6 +53,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! */ @@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref) static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy, - bool *hit); + bool *hit, Relation rel); static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy); static void PinBuffer_Locked(BufferDesc *buf); static void UnpinBuffer(BufferDesc *buf, bool fixOwner); @@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, */ pgstat_count_buffer_read(reln); buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, - forkNum, blockNum, mode, strategy, &hit); + forkNum, blockNum, mode, strategy, &hit, + reln); if (hit) pgstat_count_buffer_hit(reln); return buf; @@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, Assert(InRecovery); return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum, - mode, strategy, &hit); + mode, strategy, &hit, NULL); } @@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, static Buffer ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, - BufferAccessStrategy strategy, bool *hit) + BufferAccessStrategy strategy, bool *hit, + Relation rel) { BufferDesc *bufHdr; Block bufBlock; @@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum, isExtend = (blockNum == P_NEW); + /* create storage when first read data page for gtt */ + if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && + (isExtend || blockNum == 0) && + forkNum == MAIN_FORKNUM && + !gtt_storage_attached(smgr->smgr_rnode.node.relNode)) + { + RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel); + } + TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum, smgr->smgr_rnode.node.spcNode, smgr->smgr_rnode.node.dbNode, @@ -2809,6 +2822,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(relation->rd_node.relNode)) + { + 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 c3adb2e..eb95e5f 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -62,6 +62,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)))) @@ -4087,3 +4088,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 32df8c8..e4e1125 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/storage/smgr/md.c b/src/backend/storage/smgr/md.c index c5b771c..e7725ce 100644 --- a/src/backend/storage/smgr/md.c +++ b/src/backend/storage/smgr/md.c @@ -646,6 +646,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, */ if (zero_damaged_pages || InRecovery) MemSet(buffer, 0, BLCKSZ); + else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM) + { + /* global temp table init btree meta page */ + MemSet(buffer, 0, BLCKSZ); + mdwrite(reln, forknum, blocknum, buffer, true); + } else ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), 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 7c6f057..eb10cf2 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" @@ -4578,12 +4579,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)) { @@ -4708,15 +4722,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)) { @@ -6098,6 +6124,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; @@ -6115,6 +6142,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, @@ -6126,6 +6160,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; @@ -6141,6 +6177,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, @@ -7054,6 +7097,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)) @@ -7066,6 +7111,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 = @@ -7078,6 +7131,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. @@ -7097,6 +7152,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 1e3e6d3..345e3e9 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -34,6 +34,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" @@ -2878,6 +2879,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 df025a5..8df9c7c 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -63,6 +63,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" @@ -1124,6 +1125,29 @@ 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(); + /* + * For global temp table, all backend can use + * this relation, so rd_islocaltemp is true + * in every backend. + */ + relation->rd_islocaltemp = true; + 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); @@ -1178,6 +1202,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: @@ -2217,6 +2242,8 @@ RelationReloadIndexInfo(Relation relation) HeapTupleHeaderGetXmin(tuple->t_data)); ReleaseSysCache(tuple); + + gtt_fix_index_state(relation); } /* Okay, now it's valid again */ @@ -3313,6 +3340,15 @@ RelationBuildLocalRelation(const char *relname, rel->rd_backend = BackendIdForTempRelations(); rel->rd_islocaltemp = true; break; + case RELPERSISTENCE_GLOBAL_TEMP: + rel->rd_backend = BackendIdForTempRelations(); + /* + * For global temp table, all backend can use + * this relation, so rd_islocaltemp is true + * in every backend. + */ + rel->rd_islocaltemp = true; + break; default: elog(ERROR, "invalid relpersistence: %c", relpersistence); break; @@ -3427,6 +3463,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence) TransactionId freezeXid = InvalidTransactionId; RelFileNode newrnode; + if (RELATION_IS_GLOBAL_TEMP(relation)) + elog(ERROR, "global temp table does not allow setting new relfilenode"); + /* Allocate a new relfilenode */ newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL, persistence); @@ -3467,7 +3506,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; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index cacbe90..9c4220b 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -140,6 +140,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, @@ -2012,6 +2024,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 ec3e2c6..d3697d2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -15583,6 +15583,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo) { char *ftoptions = NULL; char *srvname = NULL; + char *table_type = NULL; switch (tbinfo->relkind) { @@ -15634,9 +15635,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); diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index f3c7eb9..28134e2 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -3723,7 +3723,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 dc03fbd..8bd6d09 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -1016,6 +1016,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}, @@ -2378,6 +2380,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" */ @@ -2586,6 +2591,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 a12fc1f..78958e4 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -165,6 +165,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 2228256..4d5d13a 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -5504,6 +5504,40 @@ proparallel => 'r', prorettype => 'float8', proargtypes => 'oid', prosrc => 'pg_stat_get_xact_function_self_time' }, +# For global temporary table +{ oid => '4191', + 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 => '4192', + 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,int4,float4,int4,xid,xid}', + proargmodes => '{i,o,o,o,o,o}', + proargnames => '{relid,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}', + prosrc => 'pg_get_gtt_relstats' }, +{ oid => '4193', + 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 => '4194', + 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 048003c..af48cdf 100644 --- a/src/include/catalog/storage.h +++ b/src/include/catalog/storage.h @@ -19,7 +19,7 @@ #include "storage/smgr.h" #include "utils/relcache.h" -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 RelationTruncate(Relation rel, BlockNumber nblocks); diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h new file mode 100644 index 0000000..aa80cb5 --- /dev/null +++ b/src/include/catalog/storage_gtt.h @@ -0,0 +1,41 @@ +/*------------------------------------------------------------------------- + * + * 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 "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); +extern bool is_other_backend_use_gtt(RelFileNode node); +extern bool gtt_storage_attached(Oid relid); +extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node); +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); + +#endif /* STORAGE_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 870ecb5..92c590e 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 ce93ace..0f7262e 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -281,6 +281,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 44ed04d..b308cb8 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 @@ -277,6 +277,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 @@ -535,11 +536,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. * @@ -602,6 +605,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..50ca9ac --- /dev/null +++ b/src/test/regress/expected/gtt_clean.out @@ -0,0 +1,7 @@ +reset search_path; +drop schema gtt cascade; +NOTICE: drop cascades to 4 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 diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out new file mode 100644 index 0000000..e21e540 --- /dev/null +++ b/src/test/regress/expected/gtt_function.out @@ -0,0 +1,282 @@ +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 +); +-- ERROR +create index CONCURRENTLY idx_gtt1 on gtt1 (b); +ERROR: cannot create indexes on global temporary tables using concurrent mode +-- ERROR +cluster gtt1 using gtt1_pkey; +ERROR: not support cluster global temporary tables yet +-- ERROR +create table gtt1(a int primary key, b text) on commit delete rows; +ERROR: ON COMMIT can only be used on temporary tables +-- ok +create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true); +ERROR: regular table cannot specifie on_commit_delete_rows +-- 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'); + +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'); +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'); +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); +ERROR: The parent table must be global temporary table +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: can not defeine global temp 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; +-- ok +ALTER TABLE gtt_test ADD COLUMN address varchar(30); +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); +ERROR: referenced relation "products" is not a global temp table +-- ERROR +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); +ERROR: referenced relation "products" is not a global temp table +-- ERROR +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 +-- ALL ERROR +create index idx_err on gtt1 using hash (a); +ERROR: only support btree index on global temp table +create index idx_err on gtt1 using gist (a); +ERROR: data type integer has no default operator class for access method "gist" +HINT: You must specify an operator class for the index or define a default operator class for the data type. +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +ERROR: only support btree index on global temp table +create index idx_tmp_t0_1 on tmp_t0 using gist (c0); +ERROR: only support btree index on global temp table +--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; +--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 select * from gt1 where a=1; + QUERY PLAN +--------------------------------------------------------------------------- + Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4) + Recheck Cond: (a = 1) + -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0) + Index Cond: (a = 1) +(4 rows) + +explain select * from gt1 where a=200000; + QUERY PLAN +--------------------------------------------------------------------------- + Bitmap Heap Scan on gt1 (cost=12.17..482.50 rows=500 width=4) + Recheck Cond: (a = 200000) + -> Bitmap Index Scan on idx_gt1_1 (cost=0.00..12.04 rows=500 width=0) + Index Cond: (a = 200000) +(4 rows) + +explain select * from gt1 where a*10=300; + QUERY PLAN +--------------------------------------------------------------------------- + Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4) + Recheck Cond: ((a * 10) = 300) + -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0) + Index Cond: ((a * 10) = 300) +(4 rows) + +explain select * from gt1 where a*10=3; + QUERY PLAN +--------------------------------------------------------------------------- + Bitmap Heap Scan on gt1 (cost=12.29..483.88 rows=500 width=4) + Recheck Cond: ((a * 10) = 3) + -> Bitmap Index Scan on idx_gt1_3 (cost=0.00..12.17 rows=500 width=0) + Index Cond: ((a * 10) = 3) +(4 rows) + +analyze gt1; +explain select * from gt1 where a=1; + QUERY PLAN +-------------------------------------------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4) + Index Cond: (a = 1) +(2 rows) + +explain select * from gt1 where a=200000; + QUERY PLAN +-------------------------------------------------------------------------- + Index Only Scan using idx_gt1_1 on gt1 (cost=0.29..8.31 rows=1 width=4) + Index Cond: (a = 200000) +(2 rows) + +explain select * from gt1 where a*10=300; + QUERY PLAN +--------------------------------------------------------------------- + Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4) + Index Cond: ((a * 10) = 300) +(2 rows) + +explain select * from gt1 where a*10=3; + QUERY PLAN +--------------------------------------------------------------------- + Index Scan using idx_gt1_3 on gt1 (cost=0.42..8.44 rows=1 width=4) + Index Cond: ((a * 10) = 3) +(2 rows) + +reset search_path; +drop schema gtt_function cascade; +NOTICE: drop cascades to 17 other objects +DETAIL: drop cascades to table gtt_function.gtt1 +drop cascades to table gtt_function.gtt_test +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.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.mytable +drop cascades to table gtt_function.gtt_seq +drop cascades to table gtt_function.gt +drop cascades to table gtt_function.gt1 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..30d8a7b --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_1.out @@ -0,0 +1,84 @@ +set search_path=gtt,sys; +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..850ef3e --- /dev/null +++ b/src/test/regress/expected/gtt_parallel_2.out @@ -0,0 +1,161 @@ +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 +---+--- +(0 rows) + +begin; +select * from gtt3 order by a; + a | b +---+--- +(0 rows) + +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(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 | 475136 | 974848 +(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 | 409600 | 409600 | 409600 +(2 rows) + +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..9fe5fd4 --- /dev/null +++ b/src/test/regress/expected/gtt_prepare.out @@ -0,0 +1,9 @@ +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); +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..b258b7c --- /dev/null +++ b/src/test/regress/expected/gtt_stats.out @@ -0,0 +1,76 @@ +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(a int primary key, b text) on commit PRESERVE rows; +-- 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) + +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 0 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 0 | 0 | 0 +(1 row) + +-- 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) + +analyze gtt; +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename; + schemaname | tablename | relpages | reltuples | relallvisible +------------+-----------+----------+-----------+--------------- + gtt_stats | gtt | 55 | 10000 | 0 +(1 row) + +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 2ab2115..4f4eb46 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1349,6 +1349,93 @@ 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", 'p'::"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.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(relpages, reltuples, relallvisible, relfrozenxid, relminmxid) + WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"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"])) 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 d2b17dd..80e577f 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..f3cf710 --- /dev/null +++ b/src/test/regress/sql/gtt_clean.sql @@ -0,0 +1,6 @@ + + +reset search_path; + +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..784c537 --- /dev/null +++ b/src/test/regress/sql/gtt_function.sql @@ -0,0 +1,187 @@ + +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 +); + +-- 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; + +-- ok +create table gtt1(a int primary key, b text) with(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'); + +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'); + +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'); + +-- ok +create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent); +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; + +-- ok +ALTER TABLE gtt_test ADD COLUMN address varchar(30); + +-- ERROR +CREATE TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); + +-- ERROR +CREATE global temp TABLE orders ( + order_id integer PRIMARY KEY, + product_no integer REFERENCES products (product_no), + quantity integer +); + +-- ERROR +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; + +-- ALL ERROR +create index idx_err on gtt1 using hash (a); +create index idx_err on gtt1 using gist (a); +create index idx_tmp_t0_1 on tmp_t0 using gin (c0); +create index idx_tmp_t0_1 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; + +--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 select * from gt1 where a=1; +explain select * from gt1 where a=200000; +explain select * from gt1 where a*10=300; +explain select * from gt1 where a*10=3; +analyze gt1; +explain select * from gt1 where a=1; +explain select * from gt1 where a=200000; +explain select * from gt1 where a*10=300; +explain select * from gt1 where a*10=3; + +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..d7d81de --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_1.sql @@ -0,0 +1,42 @@ + + +set search_path=gtt,sys; + +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..5203c2b --- /dev/null +++ b/src/test/regress/sql/gtt_parallel_2.sql @@ -0,0 +1,76 @@ + + +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(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; + +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..2f4d883 --- /dev/null +++ b/src/test/regress/sql/gtt_prepare.sql @@ -0,0 +1,16 @@ + +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); +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..f041892 --- /dev/null +++ b/src/test/regress/sql/gtt_stats.sql @@ -0,0 +1,42 @@ + +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(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 0 +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename; + +-- expect 0 +select * from pg_gtt_stats order by tablename; + +analyze gtt; + +select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats order by tablename; + +select * from pg_gtt_stats order by tablename; + +reset search_path; + +drop schema gtt_stats cascade; +