global_temporary_table_v17-pg13.patch
application/octet-stream
Filename: global_temporary_table_v17-pg13.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v17
| File | + | − |
|---|---|---|
| src/backend/access/common/reloptions.c | 21 | 1 |
| src/backend/access/gist/gistutil.c | 3 | 1 |
| src/backend/access/hash/hash.c | 3 | 1 |
| src/backend/access/heap/heapam_handler.c | 2 | 2 |
| src/backend/access/heap/vacuumlazy.c | 4 | 3 |
| src/backend/access/nbtree/nbtpage.c | 5 | 0 |
| src/backend/access/transam/xlog.c | 4 | 0 |
| src/backend/catalog/catalog.c | 2 | 0 |
| src/backend/catalog/heap.c | 51 | 9 |
| src/backend/catalog/index.c | 62 | 3 |
| src/backend/catalog/Makefile | 1 | 0 |
| src/backend/catalog/namespace.c | 7 | 0 |
| src/backend/catalog/storage.c | 15 | 1 |
| src/backend/catalog/storage_gtt.c | 1261 | 0 |
| src/backend/catalog/system_views.sql | 85 | 0 |
| src/backend/commands/analyze.c | 47 | 24 |
| src/backend/commands/cluster.c | 6 | 0 |
| src/backend/commands/copy.c | 4 | 1 |
| src/backend/commands/indexcmds.c | 10 | 0 |
| src/backend/commands/lockcmds.c | 2 | 1 |
| src/backend/commands/sequence.c | 71 | 12 |
| src/backend/commands/tablecmds.c | 111 | 4 |
| src/backend/commands/vacuum.c | 63 | 5 |
| src/backend/executor/execMain.c | 3 | 0 |
| src/backend/executor/execPartition.c | 3 | 0 |
| src/backend/executor/nodeModifyTable.c | 3 | 0 |
| src/backend/optimizer/path/allpaths.c | 7 | 1 |
| src/backend/optimizer/plan/planner.c | 2 | 0 |
| src/backend/optimizer/util/plancat.c | 9 | 0 |
| src/backend/parser/analyze.c | 5 | 0 |
| src/backend/parser/gram.y | 4 | 16 |
| src/backend/parser/parse_relation.c | 47 | 0 |
| src/backend/parser/parse_utilcmd.c | 3 | 0 |
| src/backend/postmaster/autovacuum.c | 8 | 1 |
| src/backend/storage/buffer/bufmgr.c | 12 | 0 |
| src/backend/storage/ipc/ipci.c | 4 | 0 |
| src/backend/storage/ipc/procarray.c | 75 | 0 |
| src/backend/storage/lmgr/proc.c | 2 | 0 |
| src/backend/utils/adt/dbsize.c | 3 | 0 |
| src/backend/utils/adt/selfuncs.c | 78 | 15 |
| src/backend/utils/cache/lsyscache.c | 13 | 0 |
| src/backend/utils/cache/relcache.c | 30 | 1 |
| src/backend/utils/misc/guc.c | 21 | 0 |
| src/bin/pg_dump/pg_dump.c | 9 | 2 |
| src/bin/pg_upgrade/check.c | 2 | 2 |
| src/bin/pg_upgrade/info.c | 46 | 20 |
| src/bin/pg_upgrade/pg_upgrade.c | 7 | 3 |
| src/bin/pg_upgrade/pg_upgrade.h | 1 | 1 |
| src/bin/psql/describe.c | 2 | 1 |
| src/bin/psql/tab-complete.c | 7 | 0 |
| src/include/catalog/pg_class.h | 1 | 0 |
| src/include/catalog/pg_proc.dat | 34 | 0 |
| src/include/catalog/storage_gtt.h | 43 | 0 |
| src/include/catalog/storage.h | 1 | 1 |
| src/include/commands/sequence.h | 1 | 0 |
| src/include/parser/parse_relation.h | 3 | 0 |
| src/include/storage/bufpage.h | 2 | 0 |
| src/include/storage/lwlock.h | 1 | 0 |
| src/include/storage/procarray.h | 2 | 0 |
| src/include/storage/proc.h | 2 | 0 |
| src/include/utils/guc.h | 4 | 0 |
| src/include/utils/rel.h | 26 | 3 |
| src/test/regress/expected/gtt_clean.out | 8 | 0 |
| src/test/regress/expected/gtt_function.out | 322 | 0 |
| src/test/regress/expected/gtt_parallel_1.out | 90 | 0 |
| src/test/regress/expected/gtt_parallel_2.out | 168 | 0 |
| src/test/regress/expected/gtt_prepare.out | 10 | 0 |
| src/test/regress/expected/gtt_stats.out | 76 | 0 |
| src/test/regress/expected/rules.out | 87 | 0 |
| src/test/regress/parallel_schedule | 7 | 0 |
| src/test/regress/sql/gtt_clean.sql | 6 | 0 |
| src/test/regress/sql/gtt_function.sql | 217 | 0 |
| src/test/regress/sql/gtt_parallel_1.sql | 44 | 0 |
| src/test/regress/sql/gtt_parallel_2.sql | 79 | 0 |
| src/test/regress/sql/gtt_prepare.sql | 19 | 0 |
| src/test/regress/sql/gtt_stats.sql | 42 | 0 |
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c3d45c7..3f9e875 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
},
true
},
+ /*
+ * For global temp table only
+ * use ShareUpdateExclusiveLock for ensure safety
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temp table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1496,6 +1509,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)},
@@ -1596,13 +1611,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 ca52846..f0153cf 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 03c43ef..2674132 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 39b8f17..38b46d0 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -28,6 +28,7 @@
#include "access/transam.h"
#include "access/xlog.h"
#include "access/xloginsert.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
@@ -601,6 +602,10 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(rel->rd_node.relNode))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4361568..08ef041 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6348,6 +6348,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 9d9e915..1c50263 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -61,6 +61,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
@@ -99,6 +100,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -427,7 +429,7 @@ heap_create(const char *relname,
case RELKIND_INDEX:
case RELKIND_SEQUENCE:
- RelationCreateStorage(rel->rd_node, relpersistence);
+ RelationCreateStorage(rel->rd_node, relpersistence, rel);
break;
case RELKIND_RELATION:
@@ -957,6 +959,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -995,8 +998,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;
@@ -1358,6 +1371,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1938,6 +1952,14 @@ heap_drop_with_catalog(Oid relid)
if (relid == defaultPartOid)
update_default_partition_oid(parentOid, InvalidOid);
+ /* We allow to drop global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not drop relation %s when other backend attached this global temp table",
+ RelationGetRelationName(rel));
+ }
+
/*
* Schedule unlinking of the relation's physical files at commit.
*/
@@ -3165,7 +3187,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;
@@ -3177,7 +3199,7 @@ RelationTruncateIndexes(Relation heapRelation)
IndexInfo *indexInfo;
/* Open the index relation; use exclusive lock, just to be sure */
- currentIndex = index_open(indexId, AccessExclusiveLock);
+ currentIndex = index_open(indexId, lockmode);
/*
* Fetch info needed for index_build. Since we know there are no
@@ -3223,8 +3245,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 +3284,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3265,23 +3293,37 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (!gtt_storage_attached(rel->rd_node.relNode))
+ return;
+
+ /*
+ * Truncate global temp table only need RowExclusiveLock
+ */
+ lockmode = RowExclusiveLock;
+ }
+
/* Truncate the underlying relation */
table_relation_nontransactional_truncate(rel);
/* If the relation has indexes, truncate the indexes too */
- RelationTruncateIndexes(rel);
+ RelationTruncateIndexes(rel, lockmode);
/* If there is a toast table, truncate that too */
toastrelid = rel->rd_rel->reltoastrelid;
if (OidIsValid(toastrelid))
{
- Relation toastrel = table_open(toastrelid, AccessExclusiveLock);
+ Relation toastrel = table_open(toastrelid, lockmode);
table_relation_nontransactional_truncate(toastrel);
- RelationTruncateIndexes(toastrel);
+ RelationTruncateIndexes(toastrel, lockmode);
/* keep the lock... */
table_close(toastrel, NoLock);
}
+
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7223679..2a0144e 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,19 @@ index_create(Relation heapRelation,
indexRelationName, RelationGetRelationName(heapRelation))));
}
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temp table use concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot 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
*/
@@ -2061,6 +2075,14 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
*/
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
+ /* We allow to drop index on global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+ {
+ if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+ elog(ERROR, "can not drop index %s when other backend attached this global temp table.",
+ RelationGetRelationName(userHeapRelation));
+ }
+
/*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
@@ -2667,6 +2689,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,
@@ -2752,21 +2779,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);
+ }
}
/*
@@ -2880,6 +2921,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
*/
@@ -3475,6 +3525,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 5ff7824..9b58f2a 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..2982bc7 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, isCommit);
+ }
+
pfree(srels);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..04f7fcc
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1261 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "access/table.h"
+#include "access/relation.h"
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "catalog/namespace.h"
+#include "catalog/index.h"
+#include "catalog/pg_type.h"
+#include "catalog/pg_statistic.h"
+#include "commands/tablecmds.h"
+#include "commands/sequence.h"
+#include "funcapi.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "nodes/execnodes.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/procarray.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "storage/sinvaladt.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include "utils/lsyscache.h"
+#include <utils/relcache.h>
+#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, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+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_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock, bool isCommit)
+{
+ 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);
+
+ if (isCommit)
+ {
+ 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_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ /* 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 temporary table local hash",
+ MyBackendId, relid);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry->spcnode = rnode.spcNode;
+ entry->relpages = 0;
+ entry->reltuples = 0;
+ entry->relallvisible = 0;
+ entry->relfrozenxid = InvalidTransactionId;
+ entry->relminmxid = InvalidMultiXactId;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ 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();
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(rnode);
+ }
+
+ 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, bool isCommit)
+{
+ 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)
+ {
+ Assert(TransactionIdIsNormal(entry->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(entry->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ RelFileNode rnode;
+ rnode.spcNode = entry->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = entry->relid;
+ gtt_storage_checkout(rnode, false, isCommit);
+ }
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+ }
+
+ 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 ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(rnodes[i], true, false);
+ }
+ 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;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (operation != CMD_INSERT)
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_attached(relation->rd_node.relNode))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b8a3f46..491db16 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 924ef37..7efc5a9 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -34,6 +34,7 @@
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "commands/dbcommands.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -103,7 +104,7 @@ static int acquire_inherited_sample_rows(Relation onerel, int elevel,
HeapTuple *rows, int targrows,
double *totalrows, double *totaldeadrows);
static void update_attstats(Oid relid, bool inh,
- int natts, VacAttrStats **vacattrstats);
+ int natts, VacAttrStats **vacattrstats, char relpersistence);
static Datum std_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
static Datum ind_fetch_func(VacAttrStatsP stats, int rownum, bool *isNull);
@@ -184,6 +185,13 @@ analyze_rel(Oid relid, RangeVar *relation,
return;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
/*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
@@ -586,14 +594,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1456,7 +1465,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1558,31 +1567,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /* Update column statistic to localhash, not catalog */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 11ce1bb..552fd87 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/copy.c b/src/backend/commands/copy.c
index e79ede4..078694d 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -27,6 +27,7 @@
#include "catalog/dependency.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
@@ -1062,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, stmt->filename, stmt->is_program,
@@ -2840,6 +2841,8 @@ CopyFrom(CopyState cstate)
ExecOpenIndices(resultRelInfo, false);
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 3f3a89f..928c01e 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2586,6 +2586,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 d8cafc4..deecde8 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..fada1ac 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -30,6 +30,8 @@
#include "catalog/objectaccess.h"
#include "catalog/pg_sequence.h"
#include "catalog/pg_type.h"
+#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/defrem.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -94,7 +96,7 @@ static HTAB *seqhashtab = NULL; /* hash table for SeqTable items */
*/
static SeqTableData *last_used_seq = NULL;
-static void fill_seq_with_data(Relation rel, HeapTuple tuple);
+static void fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf);
static Relation lock_and_open_sequence(SeqTable seq);
static void create_seq_hashtable(void);
static void init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel);
@@ -108,6 +110,7 @@ static void init_params(ParseState *pstate, List *options, bool for_identity,
List **owned_by);
static void do_setval(Oid relid, int64 next, bool iscalled);
static void process_owned_by(Relation seqrel, List *owned_by, bool for_identity);
+int64 get_seqence_start_value(Oid seqid);
/*
@@ -222,7 +225,7 @@ DefineSequence(ParseState *pstate, CreateSeqStmt *seq)
/* now initialize the sequence's data */
tuple = heap_form_tuple(tupDesc, value, null);
- fill_seq_with_data(rel, tuple);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
/* process OWNED BY if given */
if (owned_by)
@@ -327,7 +330,7 @@ ResetSequence(Oid seq_relid)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seq_rel, tuple);
+ fill_seq_with_data(seq_rel, tuple, InvalidBuffer);
/* Clear local cache so that we don't think we have cached numbers */
/* Note that we do not change the currval() state */
@@ -340,17 +343,21 @@ ResetSequence(Oid seq_relid)
* Initialize a sequence's relation with the specified tuple as content
*/
static void
-fill_seq_with_data(Relation rel, HeapTuple tuple)
+fill_seq_with_data(Relation rel, HeapTuple tuple, Buffer buf)
{
- Buffer buf;
Page page;
sequence_magic *sm;
OffsetNumber offnum;
+ bool lockBuffer = false;
/* Initialize first page of relation with special magic number */
- buf = ReadBuffer(rel, P_NEW);
- Assert(BufferGetBlockNumber(buf) == 0);
+ if (buf == InvalidBuffer)
+ {
+ buf = ReadBuffer(rel, P_NEW);
+ Assert(BufferGetBlockNumber(buf) == 0);
+ lockBuffer = true;
+ }
page = BufferGetPage(buf);
@@ -360,7 +367,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
/* Now insert sequence tuple */
- LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
+ if (lockBuffer)
+ LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE);
/*
* Since VACUUM does not process sequences, we have to force the tuple to
@@ -410,7 +418,8 @@ fill_seq_with_data(Relation rel, HeapTuple tuple)
END_CRIT_SECTION();
- UnlockReleaseBuffer(buf);
+ if (lockBuffer)
+ UnlockReleaseBuffer(buf);
}
/*
@@ -502,7 +511,7 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
/*
* Insert the modified tuple into the new storage file.
*/
- fill_seq_with_data(seqrel, newdatatuple);
+ fill_seq_with_data(seqrel, newdatatuple, InvalidBuffer);
}
/* process OWNED BY if given */
@@ -611,7 +620,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +945,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1163,13 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(seqrel->rd_node.relNode))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1970,46 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+void
+gtt_init_seq(Relation rel)
+{
+ /* Initialize sequence for global temporary tables */
+ Datum value[SEQ_COL_LASTCOL] = {0};
+ bool null[SEQ_COL_LASTCOL] = {false};
+ HeapTuple tuple;
+ int64 startv = get_seqence_start_value(RelationGetRelid(rel));
+
+ /*
+ * last_value from pg_sequence.seqstart
+ * log_cnt = 0
+ * is_called = false
+ */
+ value[SEQ_COL_LASTVAL-1] = Int64GetDatumFast(startv); /* start sequence with 1 */
+
+ tuple = heap_form_tuple(RelationGetDescr(rel), value, null);
+ fill_seq_with_data(rel, tuple, InvalidBuffer);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 7a13b97..536083e 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"
@@ -553,6 +554,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
Relation partitionTbl);
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -598,6 +600,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
LOCKMODE parentLockmode;
const char *accessMethod = NULL;
Oid accessMethodId = InvalidOid;
+ OnCommitAction oncommit_action = ONCOMMIT_NOOP;
/*
* Truncate relname to appropriate length (probably a waste of time, as
@@ -608,8 +611,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")));
@@ -639,7 +644,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),
@@ -740,6 +747,55 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* global temp table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (relkind == RELKIND_RELATION || relkind == RELKIND_PARTITIONED_TABLE))
+ {
+ /* check parent table*/
+ if (inheritOids)
+ {
+ Oid parent = linitial_oid(inheritOids);
+ Relation relation = table_open(parent, NoLock);
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ elog(ERROR, "The parent table must be global temporary table");
+
+ table_close(relation, NoLock);
+ }
+
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temp table not support on commit drop clause");
+
+ stmt->options = lappend(stmt->options, opt);
+ }
+ }
+ else if (oncommit_action != ONCOMMIT_NOOP)
+ elog(ERROR, "The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1824,7 +1880,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);
@@ -3567,6 +3624,14 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temp table only this session use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(rel->rd_node))
+ elog(ERROR, "can not alter table %s when other backend attached this global temp table",
+ RelationGetRelationName(rel));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -8179,6 +8244,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("constraints on temporary tables must involve temporary tables of this session")));
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ if (pkrel->rd_rel->relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("constraints on global temporary tables may reference only global temporary tables")));
+ break;
}
/*
@@ -12647,6 +12718,12 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "The global temp table does not allow modification on commit action.");
+ }
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -12849,6 +12926,9 @@ ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
*/
rel = relation_open(tableOid, lockmode);
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ elog(ERROR, "not support alter table set tablespace on global temp table");
+
/*
* No work if no change in tablespace.
*/
@@ -13221,7 +13301,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,
@@ -14628,7 +14708,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",
@@ -17220,3 +17302,28 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
table_close(rel, NoLock);
}
}
+
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ if (defGetBoolean(def))
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index d625d17..f58c44d 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -35,6 +35,7 @@
#include "catalog/pg_database.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_namespace.h"
+#include "catalog/storage_gtt.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
#include "commands/vacuum.h"
@@ -1217,6 +1218,17 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = false;
+
+ /* global temp table remember relstats to localhash and rel->rd_rel, not catalog */
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ is_gtt = true;
+ up_gtt_relstats(relation,
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1230,17 +1242,26 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relpages = (int32) num_pages;
+ else if (pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (is_gtt)
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ else if (pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (is_gtt)
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ else if (pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1285,7 +1306,8 @@ vac_update_relstats(Relation relation,
* This should match vac_update_datfrozenxid() concerning what we consider
* to be "in the future".
*/
- if (TransactionIdIsNormal(frozenxid) &&
+ if (!is_gtt &&
+ TransactionIdIsNormal(frozenxid) &&
pgcform->relfrozenxid != frozenxid &&
(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
TransactionIdPrecedes(ReadNewTransactionId(),
@@ -1296,7 +1318,8 @@ vac_update_relstats(Relation relation,
}
/* Similarly for relminmxid */
- if (MultiXactIdIsValid(minmulti) &&
+ if (!is_gtt &&
+ MultiXactIdIsValid(minmulti) &&
pgcform->relminmxid != minmulti &&
(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
@@ -1394,6 +1417,10 @@ vac_update_datfrozenxid(void)
continue;
}
+ /* global temp table relstats not in pg_class */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
/*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1451,6 +1478,25 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /*
+ * Global temp table get frozenxid from MyProc
+ * to avoid the vacuum truncate clog that gtt need.
+ */
+ if (max_active_gtt > 0)
+ {
+ TransactionId oldest_gtt_frozenxid =
+ list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+ TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest FrozenXid is far in the past"),
+ errhint("please truncate them or kill those sessions that use them.")));
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1778,6 +1824,18 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
return false;
}
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(onerel->rd_node.relNode))
+ {
+ ereport(WARNING,
+ (errmsg("skipping vacuum empty global temp table \"%s\"",
+ RelationGetRelationName(onerel))));
+ relation_close(onerel, lmode);
+ PopActiveSnapshot();
+ CommitTransactionCommand();
+ return false;
+ }
+
/*
* Silently ignore tables that are temp tables of other backends ---
* trying to vacuum these will lead to great unhappiness, since their
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 28130fb..dcb4a0b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,9 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index c13b1d3..253054d 100644
--- a/src/backend/executor/execPartition.c
+++ b/src/backend/executor/execPartition.c
@@ -18,6 +18,7 @@
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "foreign/fdwapi.h"
@@ -543,6 +544,8 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ init_gtt_storage(mtstate->operation, leaf_part_rri);
+
/*
* Build WITH CHECK OPTION constraints for the partition. Note that we
* didn't build the withCheckOptionList for partitions within the planner,
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index d71c0a4..0900907 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
+#include "catalog/storage_gtt.h"
#include "commands/trigger.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
@@ -2372,6 +2373,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ init_gtt_storage(operation, resultRelInfo);
+
/*
* If this is an UPDATE and a BEFORE UPDATE trigger is present, the
* trigger itself might modify the partition-key values. So arrange
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 905bbe7..170963d 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 b44efd6..14dbaaf 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
+ /* global temp table is same as local temp table */
if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index d82fc5a..93c6d97 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -28,6 +28,7 @@
#include "catalog/catalog.h"
#include "catalog/dependency.h"
#include "catalog/heap.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -221,6 +222,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temp table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(indexRelation->rd_node.relNode))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6676412..0685c1c 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2585,6 +2585,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("materialized views must not use temporary tables or views")));
+ if (is_query_using_gtt(query))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialized views must not use global temporary tables or views")));
+
/*
* A materialized view would either need to save parameters for use in
* maintaining/loading the data or prohibit them entirely. The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 96e7fdb..30f585c 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; }
@@ -11594,19 +11588,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 af77f18..67d34f0 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 e3a43d3..ca460ca 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2087,6 +2087,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);
@@ -2153,7 +2158,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 5880054..98f7b43 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! */
@@ -2809,6 +2811,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 4a5b26c..9754168 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 eb321f7..3893cef 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -395,6 +395,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
@@ -577,6 +578,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->session_gtt_frozenxid = InvalidTransactionId; /* init session level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyPgXact->delayChkpt = false;
MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/utils/adt/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 0be26fe..c169c99 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"
@@ -4570,12 +4571,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))
{
@@ -4700,15 +4714,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))
{
@@ -6090,6 +6116,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;
@@ -6107,6 +6134,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,
@@ -6118,6 +6152,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;
@@ -6133,6 +6169,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,
@@ -7046,6 +7089,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))
@@ -7058,6 +7103,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 =
@@ -7070,6 +7123,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.
@@ -7089,6 +7144,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 3da90cb..97240ff 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 ff70326..1cf7063 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"
@@ -1127,6 +1128,24 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
relation->rd_islocaltemp = false;
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ {
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+
+ relation->rd_backend = BackendIdForTempRelations();
+ relation->rd_islocaltemp = false;
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+ relation->rd_rel->relpages = (int32)relpages;
+ relation->rd_rel->reltuples = (float4)reltuples;
+ relation->rd_rel->relallvisible = (int32)relallvisible;
+ }
+ break;
default:
elog(ERROR, "invalid relpersistence: %c",
relation->rd_rel->relpersistence);
@@ -1181,6 +1200,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:
@@ -2220,6 +2240,8 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ gtt_fix_index_state(relation);
}
/* Okay, now it's valid again */
@@ -3316,6 +3338,10 @@ RelationBuildLocalRelation(const char *relname,
rel->rd_backend = BackendIdForTempRelations();
rel->rd_islocaltemp = true;
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ rel->rd_backend = BackendIdForTempRelations();
+ rel->rd_islocaltemp = false;
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relpersistence);
break;
@@ -3430,6 +3456,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);
@@ -3470,7 +3499,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 dbecc00..6acf3f3 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,
@@ -2047,6 +2059,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 a12c8d0..58b22ea 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15595,6 +15595,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
{
char *ftoptions = NULL;
char *srvname = NULL;
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15646,9 +15647,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/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 5f9a102..3e40297 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -84,7 +84,7 @@ check_and_dump_old_cluster(bool live_check)
start_postmaster(&old_cluster, true);
/* Extract a list of databases and tables from the old cluster */
- get_db_and_rel_infos(&old_cluster);
+ get_db_and_rel_infos(&old_cluster, true);
init_tablespaces();
@@ -157,7 +157,7 @@ check_and_dump_old_cluster(bool live_check)
void
check_new_cluster(void)
{
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, false);
check_new_cluster_is_empty();
check_databases_are_compatible();
diff --git a/src/bin/pg_upgrade/info.c b/src/bin/pg_upgrade/info.c
index 7e524ea..371168f 100644
--- a/src/bin/pg_upgrade/info.c
+++ b/src/bin/pg_upgrade/info.c
@@ -21,7 +21,7 @@ static void report_unmatched_relation(const RelInfo *rel, const DbInfo *db,
bool is_new_db);
static void free_db_and_rel_infos(DbInfoArr *db_arr);
static void get_db_infos(ClusterInfo *cluster);
-static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo);
+static void get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt);
static void free_rel_infos(RelInfoArr *rel_arr);
static void print_db_infos(DbInfoArr *dbinfo);
static void print_rel_infos(RelInfoArr *rel_arr);
@@ -304,9 +304,11 @@ print_maps(FileNameMap *maps, int n_maps, const char *db_name)
*
* higher level routine to generate dbinfos for the database running
* on the given "port". Assumes that server is already running.
+ * for check object need check global temp table,
+ * for create object skip global temp table.
*/
void
-get_db_and_rel_infos(ClusterInfo *cluster)
+get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt)
{
int dbnum;
@@ -316,7 +318,7 @@ get_db_and_rel_infos(ClusterInfo *cluster)
get_db_infos(cluster);
for (dbnum = 0; dbnum < cluster->dbarr.ndbs; dbnum++)
- get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum]);
+ get_rel_infos(cluster, &cluster->dbarr.dbs[dbnum], skip_gtt);
if (cluster == &old_cluster)
pg_log(PG_VERBOSE, "\nsource databases:\n");
@@ -404,7 +406,7 @@ get_db_infos(ClusterInfo *cluster)
* This allows later processing to match up old and new databases efficiently.
*/
static void
-get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
+get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo, bool skip_gtt)
{
PGconn *conn = connectToServer(cluster,
dbinfo->db_name);
@@ -441,22 +443,46 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
* output, so we have to copy that system table. It's easiest to do that
* by treating it as a user table.
*/
- snprintf(query + strlen(query), sizeof(query) - strlen(query),
- "WITH regular_heap (reloid, indtable, toastheap) AS ( "
- " SELECT c.oid, 0::oid, 0::oid "
- " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
- " ON c.relnamespace = n.oid "
- " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
- /* exclude possible orphaned temp tables */
- " ((n.nspname !~ '^pg_temp_' AND "
- " n.nspname !~ '^pg_toast_temp_' AND "
- " n.nspname NOT IN ('pg_catalog', 'information_schema', "
- " 'binary_upgrade', 'pg_toast') AND "
- " c.oid >= %u::pg_catalog.oid) OR "
- " (n.nspname = 'pg_catalog' AND "
- " relname IN ('pg_largeobject') ))), ",
- FirstNormalObjectId);
+ if (skip_gtt)
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
+ else
+ {
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ "WITH regular_heap (reloid, indtable, toastheap) AS ( "
+ " SELECT c.oid, 0::oid, 0::oid "
+ " FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
+ " ON c.relnamespace = n.oid "
+ " WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
+ CppAsString2(RELKIND_MATVIEW) ") AND "
+ /* exclude possible orphaned temp tables */
+ " ((n.nspname !~ '^pg_temp_' AND "
+ " n.nspname !~ '^pg_toast_temp_' AND "
+ " n.nspname NOT IN ('pg_catalog', 'information_schema', "
+ " 'binary_upgrade', 'pg_toast') AND "
+ " c.oid >= %u::pg_catalog.oid) OR "
+ " (n.nspname = 'pg_catalog' AND "
+ " relname IN ('pg_largeobject') ))), ",
+ FirstNormalObjectId);
+ }
/*
* Add a CTE that collects OIDs of toast tables belonging to the tables
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index 70194eb..8dd9e66 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -411,7 +411,7 @@ create_new_objects(void)
set_frozenxids(true);
/* update new_cluster info now that we have objects in the databases */
- get_db_and_rel_infos(&new_cluster);
+ get_db_and_rel_infos(&new_cluster, true);
}
/*
@@ -642,7 +642,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -653,7 +655,9 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 8b90cef..d155205 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -389,7 +389,7 @@ void check_loadable_libraries(void);
FileNameMap *gen_db_file_maps(DbInfo *old_db,
DbInfo *new_db, int *nmaps, const char *old_pgdata,
const char *new_pgdata);
-void get_db_and_rel_infos(ClusterInfo *cluster);
+void get_db_and_rel_infos(ClusterInfo *cluster, bool skip_gtt);
void print_maps(FileNameMap *maps, int n,
const char *db_name);
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 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 b6b08d0..16377e6 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 78b33b2..4be89f6 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -168,6 +168,7 @@ typedef FormData_pg_class *Form_pg_class;
#define RELPERSISTENCE_PERMANENT 'p' /* regular table */
#define RELPERSISTENCE_UNLOGGED 'u' /* unlogged permanent table */
#define RELPERSISTENCE_TEMP 't' /* temporary table */
+#define RELPERSISTENCE_GLOBAL_TEMP 'g' /* global temporary table */
/* default selection for replica identity (primary key or nothing) */
#define REPLICA_IDENTITY_DEFAULT 'd'
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 7fb574f..6f8591c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5507,6 +5507,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..63f7241
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,43 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, bool isCommit);
+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);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index 3f88683..f870e9a 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -405,6 +405,8 @@ do { \
#define PageClearPrunable(page) \
(((PageHeader) (page))->pd_prune_xid = InvalidTransactionId)
+#define GlobalTempRelationPageIsNotInitialized(rel, page) \
+ ((rel)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP && PageIsNew(page))
/* ----------------------------------------------------------------
* extern declarations
diff --git a/src/include/storage/lwlock.h b/src/include/storage/lwlock.h
index 8fda8e4..172eae9 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_TBM,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_SXACT,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index d217801..8adde87 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId session_gtt_frozenxid; /* session level global temp table relfrozenxid */
+
bool isBackgroundWorker; /* true if background worker. */
/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index a5c7d0c..a86f61f 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -125,4 +125,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
#endif /* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 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..6b6d2da 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.
*
@@ -547,6 +550,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -559,6 +563,14 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -602,6 +614,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..73d3d7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,8 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..067393b
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,322 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR: 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
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temp table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: The global temp table does not allow modification on commit action.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temp table, which cannot be specified by a regular table
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: 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: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 19 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.gtt6
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to 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..0646aae
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,90 @@
+set search_path=gtt,sys;
+select nextval('gtt_with_seq_c2_seq');
+ nextval
+---------
+ 1
+(1 row)
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..b2ef23b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,168 @@
+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 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..0e21b30
--- /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
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 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 c730461..8a39594 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..c32c0bd
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,217 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test;
+
+-- 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
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..bf0b922
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,79 @@
+
+
+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;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..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;
+