[Proposal] Global temporary tables
This is the latest patch for feature GTT(v38).
Everybody, if you have any questions, please let me know.
Wenjing
Attachments:
global_temporary_table_v38-pg14.patchapplication/octet-stream; name=global_temporary_table_v38-pg14.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..9ea9a49 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 temporary table only
+ * use ShareUpdateExclusiveLock for ensure safety
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temporary table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1817,6 +1830,8 @@ bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)},
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
partitioned_table_reloptions(Datum reloptions, bool validate)
{
/*
- * There are no options for partitioned tables yet, but this is able to do
- * some validation.
+ * Add option for global temp partitioned tables.
*/
+ static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)}
+ };
+
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 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (RELATION_IS_TEMP(rel))
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* metapage, nor the first bitmap page.
*/
sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
- if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if (!RELATION_IS_TEMP(index))
sort_threshold = Min(sort_threshold, NBuffers);
else
sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index dcaea71..536aab6 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,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
@@ -642,7 +642,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 25f2d5d..e49b67b 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"
@@ -445,9 +446,13 @@ 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.
+ * Transaction information for the global temp table is stored in
+ * local hash, not catalog.
+ */
+ 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 848123d..e9f9230 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"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ /*
+ * If a global temporary table storage file is not initialized in the
+ * current session, its index does not have root page. just returns 0.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 13f1d8c..d32aabe 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6877,6 +6877,15 @@ StartupXLOG(void)
int rmid;
/*
+ * When pg backend processes crashes, those storage files that belong
+ * to global temporary tables will not be cleaned up.
+ * They will remain in the data directory. These data files no longer
+ * belong to any session and need to be cleaned up during the crash recovery.
+ */
+ if (max_active_gtt > 0)
+ RemovePgTempFiles();
+
+ /*
* Update pg_control to show that we are recovering and to show the
* selected checkpoint as the place we are starting from. We also mark
* pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
mapped_relation,
true,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..1d23b07
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_subscription.o \
pg_type.o \
storage.o \
+ storage_gtt.o \
toasting.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4cd7d76..9554dce 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,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid)
+ MultiXactId *relminmxid,
+ bool skip_create_storage)
{
bool create_storage;
Relation rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
* storage is already created, so don't do it here. Also don't create it
* for relkinds without physical storage.
*/
- if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+ if (!RELKIND_HAS_STORAGE(relkind) ||
+ OidIsValid(relfilenode) ||
+ skip_create_storage)
create_storage = false;
else
{
@@ -427,7 +432,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:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
- new_rel_reltup->relfrozenxid = relfrozenxid;
- new_rel_reltup->relminmxid = relminmxid;
+ /*
+ * The transaction information of the global temporary table is stored
+ * in the local hash table, not 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;
@@ -1291,7 +1310,8 @@ heap_create_with_catalog(const char *relname,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1397,6 +1417,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1976,6 +1997,19 @@ heap_drop_with_catalog(Oid relid)
update_default_partition_oid(parentOid, InvalidOid);
/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3238,7 +3272,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;
@@ -3250,7 +3284,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
@@ -3296,8 +3330,16 @@ heap_truncate(List *relids)
{
Oid rid = lfirst_oid(cell);
Relation rel;
+ LOCKMODE lockmode = AccessExclusiveLock;
- rel = table_open(rid, AccessExclusiveLock);
+ /*
+ * Truncate global temporary table only clears local data,
+ * so only low-level locks need to be held.
+ */
+ if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3330,6 +3372,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3338,23 +3381,47 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ /*
+ * If this GTT is not initialized in current backend, there is
+ * no needs to anything.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(rel)))
+ return;
+
+ /*
+ * Truncate GTT only clears local data, so only low-level locks
+ * need to be held.
+ */
+ 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);
}
+
+ /*
+ * After the data is cleaned up on the GTT, the transaction information
+ * for the data(stored in local hash table) is also need reset.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..463f5a3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
+ bool skip_create_storage = false;
+
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temporary table with concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex global temporary tables concurrently")));
+
+ /*
+ * For the case that some backend is applied relcache message to create
+ * an index on a global temporary table, if this table in the current
+ * backend are not initialized, the creation of index storage on the
+ * table are also skipped.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+ {
+ skip_create_storage = true;
+ flags |= INDEX_CREATE_SKIP_BUILD;
+ }
+ }
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ skip_create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
- Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
(!concurrent && !concurrent_lock_mode));
/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
+ * Allow to drop index on global temporary table when only current
+ * backend use it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+ is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop index %s or global temporary table %s",
+ RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+ errhint("Because the index is created on the global temporary table and other backend attached it.")));
+ }
+
+ /*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
HeapTuple tuple;
Form_pg_class rd_rel;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
/*
* We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
else /* don't bother for indexes */
relallvisible = 0;
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
+ /* For global temporary table */
+ if (is_gtt)
{
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
+ /* Update GTT'statistics into local relcache */
+ rel->rd_rel->relpages = (int32) relpages;
+ rel->rd_rel->reltuples = (float4) reltuples;
+ rel->rd_rel->relallvisible = (int32) relallvisible;
+
+ /* Update GTT'statistics into local hashtable */
+ up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+ InvalidTransactionId, InvalidMultiXactId);
}
- if (rd_rel->relallvisible != (int32) relallvisible)
+ else
{
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
+ if (rd_rel->relpages != (int32) relpages)
+ {
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
+
+ if (rd_rel->reltuples != (float4) reltuples)
+ {
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
+ }
+
+ if (rd_rel->relallvisible != (int32) relallvisible)
+ {
+ rd_rel->relallvisible = (int32) relallvisible;
+ dirty = true;
+ }
}
}
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
pgstat_progress_update_multi_param(6, index, val);
}
+ /* For build index on global temporary table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+ {
+ /*
+ * If the storage for the index in this session is not initialized,
+ * it needs to be created.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ /* Before create init storage, fix the local Relcache first */
+ gtt_force_enable_index(indexRelation);
+
+ Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+ /* Init storage for index */
+ RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+ }
+ }
+
/*
* Call the access method's build procedure
*/
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
if (!OidIsValid(heapId))
return;
+ /*
+ * For reindex on global temporary table, If the storage for the index
+ * in current backend is not initialized, nothing is done.
+ */
+ if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ !gtt_storage_attached(indexId))
+ {
+ /* Suppress use of the target index while rebuilding it */
+ SetReindexProcessing(heapId, indexId);
+ /* Re-allow use of target index */
+ ResetReindexProcessing();
+ return;
+ }
+
if ((options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
errmsg("cannot create temporary relation in non-temporary schema")));
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /* Do not allow create global temporary table in temporary schemas */
+ if (isAnyTempNamespace(nspid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create global temp table 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 d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
#include "access/xlogutils.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/freespace.h"
#include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
BackendId backend; /* InvalidBackendId if not a temp rel */
+ Oid temprelOid; /* InvalidOid if not a global temporary rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
* transaction aborts later on, the storage will be destroyed.
*/
SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
{
PendingRelDelete *pending;
SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
+ /*
+ * Global temporary table and local temporary table use same
+ * design on storage module.
+ */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rnode;
pending->backend = backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = false; /* delete if abort */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
AddPendingSync(&rnode);
}
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+ /*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /* Remember global temporary table storage info to localhash */
+ remember_gtt_storage_info(rnode, rel);
+ }
+
return srel;
}
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rel->rd_node;
pending->backend = rel->rd_backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = true; /* delete if commit */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
pendingDeletes = pending;
/*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /*
* NOTE: if the relation was created in this transaction, it will now be
* present in the pending-delete list twice, once with atCommit true and
* once with atCommit false. Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
+ Oid *reloids = NULL;
prev = NULL;
for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
+ reloids = palloc(sizeof(Oid) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ reloids = repalloc(reloids, sizeof(Oid) * maxrels);
}
- srels[nrels++] = srel;
+ srels[nrels] = srel;
+ reloids[nrels] = pending->temprelOid;
+ nrels++;
}
/* must explicitly free the list entry */
pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
if (nrels > 0)
{
+ int i;
+
smgrdounlinkall(srels, nrels, false);
- for (int i = 0; i < nrels; i++)
+ for (i = 0; i < nrels; i++)
+ {
smgrclose(srels[i]);
+ /* Delete global temporary table info in localhash */
+ if (gtt_storage_attached(reloids[i]))
+ forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+ }
+
pfree(srels);
+ pfree(reloids);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..0e6563d
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1507 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ * See src/backend/catalog/GTT_README for Global temparary table's
+ * requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/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;
+
+int vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+ LWLock lock;
+ int max_entry;
+ int entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+ Oid dbNode;
+ Oid relNode;
+} gtt_fnode;
+
+typedef struct
+{
+ gtt_fnode rnode;
+ Bitmapset *map;
+ /* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+ Oid relfilenode;
+ Oid spcnode;
+
+ /* pg_class stat */
+ int32 relpages;
+ float4 reltuples;
+ int32 relallvisible;
+ TransactionId relfrozenxid;
+ TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+ Oid relid;
+
+ List *relfilenode_list;
+
+ char relkind;
+ bool on_commit_delete;
+
+ /* pg_statistic */
+ int natts;
+ int *attnum;
+ HeapTuple *att_stat_tups;
+
+ Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+ int wordnum;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ wordnum = WORDNUM(MaxBackends + 1);
+ hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+ hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+ return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+ Size size = 0;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ size = MAXALIGN(sizeof(gtt_ctl_data));
+ hash_entry_size = action_gtt_shared_hash_entry_size();
+ size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+ return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+ HASHCTL info;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ gtt_shared_ctl =
+ ShmemInitStruct("gtt_shared_ctl",
+ sizeof(gtt_ctl_data),
+ &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+ LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Oid relnode = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_backend_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)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the state of the global temporary table's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be global temporary table */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this global temporary table is not initialized in the current Backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+
+ entry->attnum[i] = 0;
+ }
+
+ return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+
+ return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 2e4aa1c..6018192 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 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);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not catalog.
+ */
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
{
- /* Yes, replace it */
- stup = heap_modify_tuple(oldtup,
- RelationGetDescr(sd),
- values,
- nulls,
- replaces);
- ReleaseSysCache(oldtup);
- CatalogTupleUpdate(sd, &stup->t_self, stup);
+ up_gtt_att_statistic(relid,
+ stats->attr->attnum,
+ inh,
+ natts,
+ RelationGetDescr(sd),
+ values,
+ nulls);
}
else
{
- /* No, insert new tuple */
- stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
- CatalogTupleInsert(sd, stup);
- }
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(relid),
+ Int16GetDatum(stats->attr->attnum),
+ BoolGetDatum(inh));
- heap_freetuple(stup);
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ stup = heap_modify_tuple(oldtup,
+ RelationGetDescr(sd),
+ values,
+ nulls,
+ replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdate(sd, &stup->t_self, stup);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsert(sd, stup);
+ }
+
+ heap_freetuple(stup);
+ }
}
table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 04d12a7..f83c1d9 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/progress.h"
@@ -72,6 +73,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -367,6 +374,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -750,6 +769,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -763,6 +785,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -830,20 +855,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -911,6 +954,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1346,10 +1398,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1557,3 +1621,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index ca24620..d4d6ea8 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -561,7 +561,7 @@ DefineIndex(Oid relationId,
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
- if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
concurrent = true;
else
concurrent = false;
@@ -2490,7 +2490,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(indOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(persistence))
ReindexRelationConcurrently(indOid, options);
else
reindex_index(indOid, false, persistence,
@@ -2595,7 +2595,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
{
result = ReindexRelationConcurrently(heapOid, options);
@@ -2954,7 +2954,7 @@ ReindexMultipleInternal(List *relids, int options)
relkind != RELKIND_PARTITIONED_TABLE);
if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- relpersistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(relpersistence))
{
(void) ReindexRelationConcurrently(relid,
options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (get_rel_relkind(reloid) == RELKIND_VIEW)
+ /* Lock table command ignores global temporary table. */
+ if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+ else if (get_rel_relkind(reloid) == RELKIND_VIEW)
LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
else if (recurse)
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 632b34a..2e6c570 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"
@@ -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);
/*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqdatatuple;
HeapTuple tuple;
- HeapTuple pgstuple;
- Form_pg_sequence pgsform;
int64 startv;
/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
- pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
- if (!HeapTupleIsValid(pgstuple))
- elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
- pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
- startv = pgsform->seqstart;
- ReleaseSysCache(pgstuple);
+ startv = get_seqence_start_value(seq_relid);
/*
* Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
init_sequence(relid, &elm, &seqrel);
+ if (RELATION_IS_GLOBAL_TEMP(seqrel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+ RelationGetRelationName(seqrel))));
+ }
+
rel = table_open(SequenceRelationId, RowExclusiveLock);
seqtuple = SearchSysCacheCopy1(SEQRELID,
ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1159,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ /* Initializes the storage for sequence which the global temporary table belongs. */
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(RelationGetRelid(seqrel)))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1967,51 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+ 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);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637..1e14939 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,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"
@@ -560,6 +561,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -605,6 +607,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
@@ -616,7 +619,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !RelpersistenceTsTemp(stmt->relation->relpersistence))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -646,7 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if (RelpersistenceTsTemp(stmt->relation->relpersistence)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -747,6 +750,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* For global temporary table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (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);
+ }
+
+ /* Check oncommit clause and save to reloptions */
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temporary 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 temporary table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1368,7 +1421,7 @@ RemoveRelations(DropStmt *drop)
* relation persistence cannot be known without its OID.
*/
if (drop->concurrent &&
- get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(relOid)))
{
Assert(list_length(drop->objects) == 1 &&
drop->removeType == OBJECT_INDEX);
@@ -1574,7 +1627,16 @@ ExecuteTruncate(TruncateStmt *stmt)
Relation rel;
bool recurse = rv->inh;
Oid myrelid;
- LOCKMODE lockmode = AccessExclusiveLock;
+ LOCKMODE lockmode;
+
+ /*
+ * Truncate global temp table only cleans up the data in current backend,
+ * only low-level locks are required.
+ */
+ if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+ else
+ lockmode = AccessExclusiveLock;
myrelid = RangeVarGetRelidExtended(rv, lockmode,
0, RangeVarCallbackForTruncate,
@@ -1839,6 +1901,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
continue;
/*
+ * Skip the global temporary table that is not initialized for storage
+ * in current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ continue;
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -3682,6 +3752,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temporary table only current backend use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5005,6 +5085,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ {
+ if (tab->chgPersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change global temporary table persistence setting")));
+
+ /*
+ * The storage for the global temporary table needs to be initialized
+ * before rewrite table.
+ */
+ if(!gtt_storage_attached(tab->relid))
+ {
+ ResultRelInfo *resultRelInfo;
+ MemoryContext oldcontext;
+ MemoryContext ctx_alter_gtt;
+
+ ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+ "gtt alter table", ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, OldHeap,
+ 1, NULL, 0);
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
+ init_gtt_storage(CMD_UTILITY, resultRelInfo);
+ ExecCloseIndices(resultRelInfo);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(ctx_alter_gtt);
+ }
+ }
+
/*
* Select destination tablespace (same as original unless user
* requested a change)
@@ -8469,6 +8585,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;
}
/*
@@ -12972,6 +13094,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -13174,6 +13299,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 temporary table");
+
/*
* No work if no change in tablespace.
*/
@@ -13548,7 +13676,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,
@@ -14956,6 +15084,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17632,3 +17761,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
CacheInvalidateRelcache(rel);
}
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ bool res = false;
+ char *sval = defGetString(def);
+
+ /* It has to be a Boolean value */
+ if (!parse_bool(sval, &res))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+ if (res)
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 395e75f..693f801 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"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+ /* For global temporary table */
+ if (is_gtt)
+ {
+ /* Store relation statistics and transaction information to the localhash */
+ up_gtt_relstats(RelationGetRelid(relation),
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+
+ /* Update relation statistics to local relcache */
+ relation->rd_rel->relpages = (int32) num_pages;
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (!is_gtt &&
+ pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (!is_gtt &&
+ pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (!is_gtt &&
+ pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1301,7 +1324,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(),
@@ -1312,7 +1336,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)))
@@ -1420,6 +1445,13 @@ vac_update_datfrozenxid(void)
}
/*
+ * The relfrozenxid for a global temporary talble is stored in localhash,
+ * not pg_class, See list_all_session_gtt_frozenxids()
+ */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
+ /*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
* to not be set (i.e. set to their respective Invalid*Id)
@@ -1476,6 +1508,43 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /* If enable global temporary table */
+ if (max_active_gtt > 0)
+ {
+ TransactionId safe_age;
+ /* */
+ TransactionId oldest_gtt_frozenxid =
+ list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+ {
+ safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+ if (safe_age < FirstNormalTransactionId)
+ safe_age += FirstNormalTransactionId;
+
+ /*
+ * We tolerate that the minimum age of gtt is less than
+ * the minimum age of conventional tables, otherwise it will
+ * throw warning message.
+ */
+ if (TransactionIdIsNormal(safe_age) &&
+ TransactionIdPrecedes(safe_age, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+ oldest_gtt_frozenxid),
+ errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+ errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+ }
+
+ /*
+ * We need to ensure that the clog required by gtt is not cleand.
+ */
+ if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1811,6 +1880,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
}
/*
+ * Skip those global temporary table that are not initialized in
+ * current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(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
* contents are probably not up-to-date on disk. (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("views cannot be unlogged because they do not have storage")));
+ /* Global temporary table are not sensible. */
+ if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("views cannot be global temp because they do not have storage")));
+
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly. We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ /* This is one kind of temp table */
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..3e4bb7e 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"
@@ -606,6 +607,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ /* Init storage for partitioned global temporary table in current backend */
+ 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 e0f2428..d998df5 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"
@@ -2286,6 +2287,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ /* Init storage for global temporary table in current backend */
+ 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 84a69b0..e6b4b0a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 247f7d4..57a473d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6427,7 +6427,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
- if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ if (RELATION_IS_TEMP(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 3e94256..a32ab49 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -29,6 +29,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temporary table in current backend */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,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 efc9c99..10f2d97 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3319,17 +3319,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; }
@@ -11437,19 +11431,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 a56bd86..702bb67 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);
/*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+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 temporary 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 c709aba..26b723e 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->options = seqoptions;
/*
+ * If a sequence is bound to a global temporary table, then the sequence
+ * must been "global temporary"
+ */
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+ /*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
* clause, the "redundant options" error will point to their occurrence,
@@ -3214,6 +3221,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isforeign = false;
}
cxt.relation = stmt->relation;
+ /* Sets the table persistence to the context */
+ cxt.relation->relpersistence = RelationGetRelPersistence(rel);
cxt.rel = rel;
cxt.inhRelations = NIL;
cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index aa5b97f..a8e41d2 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2108,6 +2108,14 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /*
+ * Aotuvacuum cannot vacuum the private data stored in each backend
+ * that belongs to global temporary table, so skip them.
+ */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2174,7 +2182,7 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(classForm->relpersistence))
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index ad0d1a9..c9fa4a4 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"
@@ -2849,6 +2850,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * Returns 0 if this global temporary table is not initialized in current
+ * backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(RelationGetRelid(relation)))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
#include "access/subtrans.h"
#include "access/syncscan.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -149,6 +150,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
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* For global temporary table shared hashtable */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 94edb24..0797d32 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -5039,3 +5040,78 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ uint8 flags = 0;
+ int i = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ /* Disable in standby node */
+ 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];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+
+ if (statusFlags & flags)
+ continue;
+
+ /* Fetch all backend that is belonging to MyDatabaseId */
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->backend_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+ result = proc->backend_gtt_frozenxid;
+
+ /* save backend pid and backend level oldest relfrozenxid */
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->backend_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 108e652..8ef5b8e 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
/* LWTRANCHE_PARALLEL_APPEND: */
"ParallelAppend",
/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
- "PerXactPredicateList"
+ "PerXactPredicateList",
+ /* LWTRANCHE_GTT_CTL */
+ "GlobalTempTableControl"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7dc3911..def8956 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..db1eb07 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /*
+ * For global temporary table ,each backend has its own storage,
+ * also only sees its own storage. Use Backendid to identify them.
+ */
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 80bd60f..98b6196 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6450,6 +6478,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;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,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;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,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))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7435,6 +7493,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.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae23299..07201a0 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2993,6 +2994,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66393be..a2814e4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,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"
@@ -1135,6 +1136,28 @@ 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;
+
+ /* For global temporary table, get relstat data from localhash */
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+
+ /* And put them to local relcache */
+ 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);
@@ -1189,6 +1212,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -1313,7 +1338,22 @@ RelationInitPhysicalAddr(Relation relation)
heap_freetuple(phys_tuple);
}
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ /*
+ * For global temporary table, get the latest relfilenode
+ * from localhash and put it in relcache.
+ */
+ if (OidIsValid(newrelnode) &&
+ newrelnode != relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = newrelnode;
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ }
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
}
else
{
@@ -2255,6 +2295,9 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
}
/* Okay, now it's valid again */
@@ -3481,6 +3524,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;
@@ -3588,28 +3635,38 @@ void
RelationSetNewRelfilenode(Relation relation, char persistence)
{
Oid newrelfilenode;
- Relation pg_class;
- HeapTuple tuple;
+ Relation pg_class = NULL;
+ HeapTuple tuple = NULL;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ /*
+ * For global temporary table, storage information for the table is
+ * maintained locally, not in catalog.
+ */
+ bool update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
- /*
- * Get a writable copy of the pg_class tuple for the given relation.
- */
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ memset(&classform, 0, sizeof(classform));
- tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u",
- RelationGetRelid(relation));
- classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (update_catalog)
+ {
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ }
/*
* Schedule unlinking of the old storage at transaction commit.
@@ -3635,7 +3692,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;
@@ -3655,6 +3712,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
break;
}
+ /* For global temporary table */
+ if (!update_catalog)
+ {
+ Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ Assert(RELATION_IS_GLOBAL_TEMP(relation));
+ Assert(!RelationIsMapped(relation));
+
+ /* Make cache invalid and set new relnode to local cache. */
+ CacheInvalidateRelcache(relation);
+ relation->rd_node.relNode = relnode;
+ }
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
@@ -3664,7 +3733,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
- if (RelationIsMapped(relation))
+ else if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3710,9 +3779,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
}
- heap_freetuple(tuple);
+ if (update_catalog)
+ {
+ heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ table_close(pg_class, RowExclusiveLock);
+ }
/*
* Make the pg_class row change or relation map change visible. This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 245a347..0811b96 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
@@ -145,6 +146,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2046,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."),
@@ -2564,6 +2586,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+ NULL
+ },
+ &vacuum_gtt_defer_check_age,
+ 10000, 0, 1000000,
+ NULL, NULL, NULL
+ },
+
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dc1d41d..8a03dac 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2432,6 +2432,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data in global temporary table/sequence */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -15650,6 +15654,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
char *ftoptions = NULL;
char *srvname = NULL;
char *foreign = "";
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15703,9 +15708,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);
@@ -16091,13 +16102,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/*
+ * Transaction information for the global temporary table is not stored
+ * in the pg_class.
+ */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(tbinfo->frozenxid == 0);
+ Assert(tbinfo->minmxid == 0);
+ }
+ /*
* In binary_upgrade mode, arrange to restore the old relfrozenxid and
* relminmxid of all vacuumable relations. (While vacuum.c processes
* TOAST tables semi-independently, here we see them only as children
* of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
* child toast table is handled below.)
*/
- if (dopt->binary_upgrade &&
+ else if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_MATVIEW))
{
@@ -17029,6 +17049,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer delqry = createPQExpBuffer();
char *qseqname;
+ bool global_temp_seq = false;
qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
@@ -17038,9 +17059,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
"SELECT format_type(seqtypid, NULL), "
"seqstart, seqincrement, "
"seqmax, seqmin, "
- "seqcache, seqcycle "
- "FROM pg_catalog.pg_sequence "
- "WHERE seqrelid = '%u'::oid",
+ "seqcache, seqcycle, "
+ "c.relpersistence "
+ "FROM pg_catalog.pg_sequence s, "
+ "pg_catalog.pg_class c "
+ "WHERE seqrelid = '%u'::oid "
+ "and s.seqrelid = c.oid",
tbinfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
@@ -17085,6 +17109,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
cache = PQgetvalue(res, 0, 5);
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ if (fout->remoteVersion >= 140000)
+ global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
/* Calculate default limits for a sequence of this type */
is_ascending = (incby[0] != '-');
if (strcmp(seqtype, "smallint") == 0)
@@ -17162,9 +17189,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
else
{
- appendPQExpBuffer(query,
- "CREATE SEQUENCE %s\n",
- fmtQualifiedDumpable(tbinfo));
+ appendPQExpBuffer(query, "CREATE ");
+
+ if (global_temp_seq)
+ appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+ appendPQExpBuffer(query, "SEQUENCE %s\n",
+ fmtQualifiedDumpable(tbinfo));
if (strcmp(seqtype, "bigint") != 0)
appendPQExpBuffer(query, " AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,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();
@@ -166,7 +166,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..5725cbe 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);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
+ CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+ if (skip_gtt)
+ {
+ /* exclude global temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+ }
+
/* exclude possible orphaned temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
" n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..32eccd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,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);
}
/*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,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 14150d0..b86a2a6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3756,7 +3756,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 8afc780..761e6dd 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,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},
@@ -2434,6 +2436,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" */
@@ -2642,6 +2647,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/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid);
+ MultiXactId *relminmxid,
+ bool skip_create_storage);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
#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 e7fbda9..ac04e2a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5589,6 +5589,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4388',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
/* GUC variables */
extern int wal_skip_threshold;
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..a2fadb0
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+ 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_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,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 af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e75f6e8..e66c0e9 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -156,6 +156,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId backend_gtt_frozenxid; /* backend 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 ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_backend_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 6a20a3b..ed7d517 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,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 c5ffea4..6dda0b6 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
@@ -306,6 +306,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
@@ -571,11 +572,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.
*
@@ -583,6 +586,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ * Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+ (relpersistence == RELPERSISTENCE_TEMP || \
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep
+----------
+
+(1 row)
+
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR: views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+ 1 | 1
+ 2 | 3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2
+----+----
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 33 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
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..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+ gtt_stats | gtt_pkey | 1 | 0 | 0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+ gtt_stats | gtt_pkey | 30 | 10000 | 0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 097ff5d..27803d6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relfilenode,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7..c498e39 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
Hi
čt 26. 11. 2020 v 12:46 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:
This is the latest patch for feature GTT(v38).
Everybody, if you have any questions, please let me know.
please, co you send a rebased patch. It is broken again
Regards
Pavel
Show quoted text
Wenjing
2020年12月8日 13:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:
Hi
čt 26. 11. 2020 v 12:46 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
This is the latest patch for feature GTT(v38).
Everybody, if you have any questions, please let me know.please, co you send a rebased patch. It is broken again
Sure
This patch is base on 16c302f51235eaec05a1f85a11c1df04ef3a6785
Simplify code for getting a unicode codepoint's canonical class.
Wenjing
Show quoted text
Regards
Pavel
Wenjing
Attachments:
global_temporary_table_v39-pg14.patchapplication/octet-stream; name=global_temporary_table_v39-pg14.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..9ea9a49 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 temporary table only
+ * use ShareUpdateExclusiveLock for ensure safety
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temporary table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1817,6 +1830,8 @@ bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)},
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
partitioned_table_reloptions(Datum reloptions, bool validate)
{
/*
- * There are no options for partitioned tables yet, but this is able to do
- * some validation.
+ * Add option for global temp partitioned tables.
*/
+ static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)}
+ };
+
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 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (RELATION_IS_TEMP(rel))
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* metapage, nor the first bitmap page.
*/
sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
- if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if (!RELATION_IS_TEMP(index))
sort_threshold = Min(sort_threshold, NBuffers);
else
sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3eea215..7c33841 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,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
@@ -642,7 +642,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 25f2d5d..e49b67b 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"
@@ -445,9 +446,13 @@ 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.
+ * Transaction information for the global temp table is stored in
+ * local hash, not catalog.
+ */
+ 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 793434c..8c76b66 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"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ /*
+ * If a global temporary table storage file is not initialized in the
+ * current session, its index does not have root page. just returns 0.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 7e81ce4..f99f894 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6878,6 +6878,15 @@ StartupXLOG(void)
int rmid;
/*
+ * When pg backend processes crashes, those storage files that belong
+ * to global temporary tables will not be cleaned up.
+ * They will remain in the data directory. These data files no longer
+ * belong to any session and need to be cleaned up during the crash recovery.
+ */
+ if (max_active_gtt > 0)
+ RemovePgTempFiles();
+
+ /*
* Update pg_control to show that we are recovering and to show the
* selected checkpoint as the place we are starting from. We also mark
* pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
mapped_relation,
true,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..1d23b07
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_subscription.o \
pg_type.o \
storage.o \
+ storage_gtt.o \
toasting.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 4cd7d76..9554dce 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,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid)
+ MultiXactId *relminmxid,
+ bool skip_create_storage)
{
bool create_storage;
Relation rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
* storage is already created, so don't do it here. Also don't create it
* for relkinds without physical storage.
*/
- if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+ if (!RELKIND_HAS_STORAGE(relkind) ||
+ OidIsValid(relfilenode) ||
+ skip_create_storage)
create_storage = false;
else
{
@@ -427,7 +432,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:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
- new_rel_reltup->relfrozenxid = relfrozenxid;
- new_rel_reltup->relminmxid = relminmxid;
+ /*
+ * The transaction information of the global temporary table is stored
+ * in the local hash table, not 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;
@@ -1291,7 +1310,8 @@ heap_create_with_catalog(const char *relname,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1397,6 +1417,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1976,6 +1997,19 @@ heap_drop_with_catalog(Oid relid)
update_default_partition_oid(parentOid, InvalidOid);
/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3238,7 +3272,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;
@@ -3250,7 +3284,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
@@ -3296,8 +3330,16 @@ heap_truncate(List *relids)
{
Oid rid = lfirst_oid(cell);
Relation rel;
+ LOCKMODE lockmode = AccessExclusiveLock;
- rel = table_open(rid, AccessExclusiveLock);
+ /*
+ * Truncate global temporary table only clears local data,
+ * so only low-level locks need to be held.
+ */
+ if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3330,6 +3372,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3338,23 +3381,47 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ /*
+ * If this GTT is not initialized in current backend, there is
+ * no needs to anything.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(rel)))
+ return;
+
+ /*
+ * Truncate GTT only clears local data, so only low-level locks
+ * need to be held.
+ */
+ 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);
}
+
+ /*
+ * After the data is cleaned up on the GTT, the transaction information
+ * for the data(stored in local hash table) is also need reset.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..463f5a3 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
+ bool skip_create_storage = false;
+
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temporary table with concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex global temporary tables concurrently")));
+
+ /*
+ * For the case that some backend is applied relcache message to create
+ * an index on a global temporary table, if this table in the current
+ * backend are not initialized, the creation of index storage on the
+ * table are also skipped.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+ {
+ skip_create_storage = true;
+ flags |= INDEX_CREATE_SKIP_BUILD;
+ }
+ }
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ skip_create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
- Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
(!concurrent && !concurrent_lock_mode));
/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
+ * Allow to drop index on global temporary table when only current
+ * backend use it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+ is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop index %s or global temporary table %s",
+ RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+ errhint("Because the index is created on the global temporary table and other backend attached it.")));
+ }
+
+ /*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
HeapTuple tuple;
Form_pg_class rd_rel;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
/*
* We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
else /* don't bother for indexes */
relallvisible = 0;
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
+ /* For global temporary table */
+ if (is_gtt)
{
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
+ /* Update GTT'statistics into local relcache */
+ rel->rd_rel->relpages = (int32) relpages;
+ rel->rd_rel->reltuples = (float4) reltuples;
+ rel->rd_rel->relallvisible = (int32) relallvisible;
+
+ /* Update GTT'statistics into local hashtable */
+ up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+ InvalidTransactionId, InvalidMultiXactId);
}
- if (rd_rel->relallvisible != (int32) relallvisible)
+ else
{
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
+ if (rd_rel->relpages != (int32) relpages)
+ {
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
+
+ if (rd_rel->reltuples != (float4) reltuples)
+ {
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
+ }
+
+ if (rd_rel->relallvisible != (int32) relallvisible)
+ {
+ rd_rel->relallvisible = (int32) relallvisible;
+ dirty = true;
+ }
}
}
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
pgstat_progress_update_multi_param(6, index, val);
}
+ /* For build index on global temporary table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+ {
+ /*
+ * If the storage for the index in this session is not initialized,
+ * it needs to be created.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ /* Before create init storage, fix the local Relcache first */
+ gtt_force_enable_index(indexRelation);
+
+ Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+ /* Init storage for index */
+ RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+ }
+ }
+
/*
* Call the access method's build procedure
*/
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
if (!OidIsValid(heapId))
return;
+ /*
+ * For reindex on global temporary table, If the storage for the index
+ * in current backend is not initialized, nothing is done.
+ */
+ if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ !gtt_storage_attached(indexId))
+ {
+ /* Suppress use of the target index while rebuilding it */
+ SetReindexProcessing(heapId, indexId);
+ /* Re-allow use of target index */
+ ResetReindexProcessing();
+ return;
+ }
+
if ((options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
errmsg("cannot create temporary relation in non-temporary schema")));
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /* Do not allow create global temporary table in temporary schemas */
+ if (isAnyTempNamespace(nspid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create global temp table 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 d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
#include "access/xlogutils.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/freespace.h"
#include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
BackendId backend; /* InvalidBackendId if not a temp rel */
+ Oid temprelOid; /* InvalidOid if not a global temporary rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
* transaction aborts later on, the storage will be destroyed.
*/
SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
{
PendingRelDelete *pending;
SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
+ /*
+ * Global temporary table and local temporary table use same
+ * design on storage module.
+ */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rnode;
pending->backend = backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = false; /* delete if abort */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
AddPendingSync(&rnode);
}
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+ /*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /* Remember global temporary table storage info to localhash */
+ remember_gtt_storage_info(rnode, rel);
+ }
+
return srel;
}
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rel->rd_node;
pending->backend = rel->rd_backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = true; /* delete if commit */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
pendingDeletes = pending;
/*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /*
* NOTE: if the relation was created in this transaction, it will now be
* present in the pending-delete list twice, once with atCommit true and
* once with atCommit false. Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
+ Oid *reloids = NULL;
prev = NULL;
for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
+ reloids = palloc(sizeof(Oid) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ reloids = repalloc(reloids, sizeof(Oid) * maxrels);
}
- srels[nrels++] = srel;
+ srels[nrels] = srel;
+ reloids[nrels] = pending->temprelOid;
+ nrels++;
}
/* must explicitly free the list entry */
pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
if (nrels > 0)
{
+ int i;
+
smgrdounlinkall(srels, nrels, false);
- for (int i = 0; i < nrels; i++)
+ for (i = 0; i < nrels; i++)
+ {
smgrclose(srels[i]);
+ /* Delete global temporary table info in localhash */
+ if (gtt_storage_attached(reloids[i]))
+ forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+ }
+
pfree(srels);
+ pfree(reloids);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..0e6563d
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1507 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ * See src/backend/catalog/GTT_README for Global temparary table's
+ * requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/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;
+
+int vacuum_gtt_defer_check_age = 0;
+
+typedef struct gtt_ctl_data
+{
+ LWLock lock;
+ int max_entry;
+ int entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+ Oid dbNode;
+ Oid relNode;
+} gtt_fnode;
+
+typedef struct
+{
+ gtt_fnode rnode;
+ Bitmapset *map;
+ /* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+ Oid relfilenode;
+ Oid spcnode;
+
+ /* pg_class stat */
+ int32 relpages;
+ float4 reltuples;
+ int32 relallvisible;
+ TransactionId relfrozenxid;
+ TransactionId relminmxid;
+} gtt_relfilenode;
+
+typedef struct
+{
+ Oid relid;
+
+ List *relfilenode_list;
+
+ char relkind;
+ bool on_commit_delete;
+
+ /* pg_statistic */
+ int natts;
+ int *attnum;
+ HeapTuple *att_stat_tups;
+
+ Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_reset_statistics(gtt_local_hash_entry *entry);
+static void gtt_free_statistics(gtt_local_hash_entry *entry);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+ int wordnum;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ wordnum = WORDNUM(MaxBackends + 1);
+ hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+ hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+ return hash_entry_size;
+}
+
+Size
+active_gtt_shared_hash_size(void)
+{
+ Size size = 0;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ size = MAXALIGN(sizeof(gtt_ctl_data));
+ hash_entry_size = action_gtt_shared_hash_entry_size();
+ size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+ return size;
+}
+
+void
+active_gtt_shared_hash_init(void)
+{
+ HASHCTL info;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ gtt_shared_ctl =
+ ShmemInitStruct("gtt_shared_ctl",
+ sizeof(gtt_ctl_data),
+ &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+ LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (found == false)
+ {
+ int wordnum;
+
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ /* First time through: initialize the hash table */
+ HASHCTL ctl;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_relstats_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt relstats context",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+ int natts = 0;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->natts = 0;
+ entry->attnum = NULL;
+ entry->att_stat_tups = NULL;
+ entry->oldrelid = InvalidOid;
+
+ natts = RelationGetNumberOfAttributes(rel);
+ entry->attnum = palloc0(sizeof(int) * natts);
+ entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ entry->natts = natts;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* only heap contain transaction information */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ gtt_reset_statistics(entry);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ pfree(d_rnode);
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+ }
+
+ gtt_free_statistics(entry);
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+ else
+ gtt_reset_statistics(entry);
+
+ return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible)
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible)
+ * from local hashtable.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ if (entry->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ Assert(entry->relid == reloid);
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == 0)
+ {
+ entry->attnum[i] = attnum;
+ break;
+ }
+ else if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < entry->natts);
+ Assert(entry->att_stat_tups[i] == NULL);
+ entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->attnum[i] == attnum)
+ {
+ Assert(entry->att_stat_tups[i]);
+ return entry->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ HeapTuple tuple;
+ Relation rel = NULL;
+ int attnum = PG_GETARG_INT32(1);
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Tuplestorestate *tupstore;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ tuple = get_gtt_att_statistic(reloid, attnum, false);
+ if (tuple)
+ {
+ Datum values[31];
+ bool isnull[31];
+ HeapTuple res = NULL;
+
+ memset(&values, 0, sizeof(values));
+ memset(&isnull, 0, sizeof(isnull));
+ heap_deform_tuple(tuple, sd, values, isnull);
+ res = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, res);
+ }
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+ tuplestore_donestoring(tupstore);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ double reltuples = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ Oid relnode = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ TupleDesc tupdesc;
+ Tuplestorestate *tupstore;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ int i = 0;
+ int j = 0;
+ uint32 oldest = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ if (max_active_gtt <= 0)
+ return (Datum) 0;
+
+ if (RecoveryInProgress())
+ return (Datum) 0;
+
+ pids = palloc0(sizeof(int) * num_xid);
+ xids = palloc0(sizeof(int) * num_xid);
+ oldest = list_all_backend_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)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the state of the global temporary table's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be global temporary table */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this global temporary table is not initialized in the current Backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ for (i = 0; i < resultRelInfo->ri_NumIndices; i++)
+ {
+ Relation index = resultRelInfo->ri_IndexRelationDescs[i];
+ IndexInfo *info = resultRelInfo->ri_IndexRelationInfo[i];
+
+ Assert(index->rd_index->indisvalid);
+ Assert(index->rd_index->indislive);
+ Assert(index->rd_index->indisready);
+
+ index_build(relation, index, info, true, false);
+ }
+
+ toastrelid = relation->rd_rel->reltoastrelid;
+ if (OidIsValid(toastrelid))
+ {
+ Relation toastrel;
+ ListCell *indlist;
+
+ toastrel = table_open(toastrelid, RowExclusiveLock);
+ RelationCreateStorage(toastrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, toastrel);
+
+ foreach(indlist, RelationGetIndexList(toastrel))
+ {
+ Oid indexId = lfirst_oid(indlist);
+ Relation currentIndex;
+ IndexInfo *indexInfo;
+
+ currentIndex = index_open(indexId, RowExclusiveLock);
+
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+static void
+gtt_reset_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+
+ entry->attnum[i] = 0;
+ }
+
+ return;
+}
+
+static void
+gtt_free_statistics(gtt_local_hash_entry *entry)
+{
+ int i;
+
+ for (i = 0; i < entry->natts; i++)
+ {
+ if (entry->att_stat_tups[i])
+ {
+ heap_freetuple(entry->att_stat_tups[i]);
+ entry->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (entry->attnum)
+ pfree(entry->attnum);
+
+ if (entry->att_stat_tups)
+ pfree(entry->att_stat_tups);
+
+ return;
+}
+
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c21..6624fc8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 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);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not 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 fd5a6ee..42c228f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -389,6 +396,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -772,6 +791,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -785,6 +807,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -852,20 +877,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -933,6 +976,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1368,10 +1420,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1579,3 +1643,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 14d24b3..93490fb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -561,7 +561,7 @@ DefineIndex(Oid relationId,
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
- if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
concurrent = true;
else
concurrent = false;
@@ -2528,7 +2528,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(indOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(persistence))
ReindexRelationConcurrently(indOid, options);
else
reindex_index(indOid, false, persistence,
@@ -2633,7 +2633,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
{
result = ReindexRelationConcurrently(heapOid, options);
@@ -2992,7 +2992,7 @@ ReindexMultipleInternal(List *relids, int options)
relkind != RELKIND_PARTITIONED_TABLE);
if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- relpersistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(relpersistence))
{
(void) ReindexRelationConcurrently(relid,
options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (get_rel_relkind(reloid) == RELKIND_VIEW)
+ /* Lock table command ignores global temporary table. */
+ if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+ else if (get_rel_relkind(reloid) == RELKIND_VIEW)
LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
else if (recurse)
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 632b34a..2e6c570 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"
@@ -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);
/*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqdatatuple;
HeapTuple tuple;
- HeapTuple pgstuple;
- Form_pg_sequence pgsform;
int64 startv;
/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
- pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
- if (!HeapTupleIsValid(pgstuple))
- elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
- pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
- startv = pgsform->seqstart;
- ReleaseSysCache(pgstuple);
+ startv = get_seqence_start_value(seq_relid);
/*
* Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
init_sequence(relid, &elm, &seqrel);
+ if (RELATION_IS_GLOBAL_TEMP(seqrel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+ RelationGetRelationName(seqrel))));
+ }
+
rel = table_open(SequenceRelationId, RowExclusiveLock);
seqtuple = SearchSysCacheCopy1(SEQRELID,
ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1154,6 +1159,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ /* Initializes the storage for sequence which the global temporary table belongs. */
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(RelationGetRelid(seqrel)))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1954,3 +1967,51 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+ 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);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 46f1637..1e14939 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,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"
@@ -560,6 +561,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -605,6 +607,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
@@ -616,7 +619,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !RelpersistenceTsTemp(stmt->relation->relpersistence))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -646,7 +649,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if (RelpersistenceTsTemp(stmt->relation->relpersistence)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -747,6 +750,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* For global temporary table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (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);
+ }
+
+ /* Check oncommit clause and save to reloptions */
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temporary 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 temporary table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1368,7 +1421,7 @@ RemoveRelations(DropStmt *drop)
* relation persistence cannot be known without its OID.
*/
if (drop->concurrent &&
- get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(relOid)))
{
Assert(list_length(drop->objects) == 1 &&
drop->removeType == OBJECT_INDEX);
@@ -1574,7 +1627,16 @@ ExecuteTruncate(TruncateStmt *stmt)
Relation rel;
bool recurse = rv->inh;
Oid myrelid;
- LOCKMODE lockmode = AccessExclusiveLock;
+ LOCKMODE lockmode;
+
+ /*
+ * Truncate global temp table only cleans up the data in current backend,
+ * only low-level locks are required.
+ */
+ if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+ else
+ lockmode = AccessExclusiveLock;
myrelid = RangeVarGetRelidExtended(rv, lockmode,
0, RangeVarCallbackForTruncate,
@@ -1839,6 +1901,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
continue;
/*
+ * Skip the global temporary table that is not initialized for storage
+ * in current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ continue;
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -3682,6 +3752,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temporary table only current backend use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5005,6 +5085,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ {
+ if (tab->chgPersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change global temporary table persistence setting")));
+
+ /*
+ * The storage for the global temporary table needs to be initialized
+ * before rewrite table.
+ */
+ if(!gtt_storage_attached(tab->relid))
+ {
+ ResultRelInfo *resultRelInfo;
+ MemoryContext oldcontext;
+ MemoryContext ctx_alter_gtt;
+
+ ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+ "gtt alter table", ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, OldHeap,
+ 1, NULL, 0);
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
+ init_gtt_storage(CMD_UTILITY, resultRelInfo);
+ ExecCloseIndices(resultRelInfo);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(ctx_alter_gtt);
+ }
+ }
+
/*
* Select destination tablespace (same as original unless user
* requested a change)
@@ -8469,6 +8585,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;
}
/*
@@ -12972,6 +13094,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -13174,6 +13299,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 temporary table");
+
/*
* No work if no change in tablespace.
*/
@@ -13548,7 +13676,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,
@@ -14956,6 +15084,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17632,3 +17761,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
CacheInvalidateRelcache(rel);
}
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ bool res = false;
+ char *sval = defGetString(def);
+
+ /* It has to be a Boolean value */
+ if (!parse_bool(sval, &res))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+ if (res)
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 98270a1..66533d6 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"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+ /* For global temporary table */
+ if (is_gtt)
+ {
+ /* Store relation statistics and transaction information to the localhash */
+ up_gtt_relstats(RelationGetRelid(relation),
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+
+ /* Update relation statistics to local relcache */
+ relation->rd_rel->relpages = (int32) num_pages;
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (!is_gtt &&
+ pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (!is_gtt &&
+ pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (!is_gtt &&
+ pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1301,7 +1324,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(),
@@ -1312,7 +1336,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)))
@@ -1421,6 +1446,13 @@ vac_update_datfrozenxid(void)
}
/*
+ * The relfrozenxid for a global temporary talble is stored in localhash,
+ * not pg_class, See list_all_session_gtt_frozenxids()
+ */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
+ /*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
* to not be set (i.e. set to their respective Invalid*Id)
@@ -1477,6 +1509,43 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /* If enable global temporary table */
+ if (max_active_gtt > 0)
+ {
+ TransactionId safe_age;
+ /* */
+ TransactionId oldest_gtt_frozenxid =
+ list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+ {
+ safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+ if (safe_age < FirstNormalTransactionId)
+ safe_age += FirstNormalTransactionId;
+
+ /*
+ * We tolerate that the minimum age of gtt is less than
+ * the minimum age of conventional tables, otherwise it will
+ * throw warning message.
+ */
+ if (TransactionIdIsNormal(safe_age) &&
+ TransactionIdPrecedes(safe_age, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+ oldest_gtt_frozenxid),
+ errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+ errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+ }
+
+ /*
+ * We need to ensure that the clog required by gtt is not cleand.
+ */
+ if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1829,6 +1898,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
}
/*
+ * Skip those global temporary table that are not initialized in
+ * current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(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
* contents are probably not up-to-date on disk. (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("views cannot be unlogged because they do not have storage")));
+ /* Global temporary table are not sensible. */
+ if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("views cannot be global temp because they do not have storage")));
+
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly. We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ /* This is one kind of temp table */
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 86594bd..3e4bb7e 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"
@@ -606,6 +607,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ /* Init storage for partitioned global temporary table in current backend */
+ 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 e0f2428..d998df5 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"
@@ -2286,6 +2287,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ /* Init storage for global temporary table in current backend */
+ 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 84a69b0..e6b4b0a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58..df46f36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6430,7 +6430,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
- if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ if (RELATION_IS_TEMP(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 3e94256..a32ab49 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -29,6 +29,7 @@
#include "catalog/dependency.h"
#include "catalog/heap.h"
#include "catalog/index.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temporary table in current backend */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,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 8f341ac..48c399c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3311,17 +3311,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; }
@@ -11394,19 +11388,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 a56bd86..702bb67 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);
/*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+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 temporary 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 89ee990..6e61a41 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->options = seqoptions;
/*
+ * If a sequence is bound to a global temporary table, then the sequence
+ * must been "global temporary"
+ */
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+ /*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
* clause, the "redundant options" error will point to their occurrence,
@@ -3222,6 +3229,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isforeign = false;
}
cxt.relation = stmt->relation;
+ /* Sets the table persistence to the context */
+ cxt.relation->relpersistence = RelationGetRelPersistence(rel);
cxt.rel = rel;
cxt.inhRelations = NIL;
cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7e28944..9aed37e 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2112,6 +2112,14 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /*
+ * Aotuvacuum cannot vacuum the private data stored in each backend
+ * that belongs to global temporary table, so skip them.
+ */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2178,7 +2186,7 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(classForm->relpersistence))
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index ad0d1a9..c9fa4a4 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"
@@ -2849,6 +2850,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * Returns 0 if this global temporary table is not initialized in current
+ * backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(RelationGetRelid(relation)))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
#include "access/subtrans.h"
#include "access/syncscan.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -149,6 +150,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
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* For global temporary table shared hashtable */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index ee912b9..299ed04 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -5037,3 +5038,78 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ uint8 flags = 0;
+ int i = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ /* Disable in standby node */
+ 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];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+
+ if (statusFlags & flags)
+ continue;
+
+ /* Fetch all backend that is belonging to MyDatabaseId */
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->backend_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+ result = proc->backend_gtt_frozenxid;
+
+ /* save backend pid and backend level oldest relfrozenxid */
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->backend_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 108e652..8ef5b8e 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
/* LWTRANCHE_PARALLEL_APPEND: */
"ParallelAppend",
/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
- "PerXactPredicateList"
+ "PerXactPredicateList",
+ /* LWTRANCHE_GTT_CTL */
+ "GlobalTempTableControl"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7dc3911..def8956 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..db1eb07 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /*
+ * For global temporary table ,each backend has its own storage,
+ * also only sees its own storage. Use Backendid to identify them.
+ */
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 80bd60f..98b6196 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6450,6 +6478,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;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,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;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,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))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7435,6 +7493,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.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ae23299..07201a0 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -2993,6 +2994,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 66393be..a2814e4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,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"
@@ -1135,6 +1136,28 @@ 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;
+
+ /* For global temporary table, get relstat data from localhash */
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+
+ /* And put them to local relcache */
+ 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);
@@ -1189,6 +1212,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -1313,7 +1338,22 @@ RelationInitPhysicalAddr(Relation relation)
heap_freetuple(phys_tuple);
}
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ /*
+ * For global temporary table, get the latest relfilenode
+ * from localhash and put it in relcache.
+ */
+ if (OidIsValid(newrelnode) &&
+ newrelnode != relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = newrelnode;
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ }
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
}
else
{
@@ -2255,6 +2295,9 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
}
/* Okay, now it's valid again */
@@ -3481,6 +3524,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;
@@ -3588,28 +3635,38 @@ void
RelationSetNewRelfilenode(Relation relation, char persistence)
{
Oid newrelfilenode;
- Relation pg_class;
- HeapTuple tuple;
+ Relation pg_class = NULL;
+ HeapTuple tuple = NULL;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ /*
+ * For global temporary table, storage information for the table is
+ * maintained locally, not in catalog.
+ */
+ bool update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
- /*
- * Get a writable copy of the pg_class tuple for the given relation.
- */
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ memset(&classform, 0, sizeof(classform));
- tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u",
- RelationGetRelid(relation));
- classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (update_catalog)
+ {
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ }
/*
* Schedule unlinking of the old storage at transaction commit.
@@ -3635,7 +3692,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;
@@ -3655,6 +3712,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
break;
}
+ /* For global temporary table */
+ if (!update_catalog)
+ {
+ Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ Assert(RELATION_IS_GLOBAL_TEMP(relation));
+ Assert(!RelationIsMapped(relation));
+
+ /* Make cache invalid and set new relnode to local cache. */
+ CacheInvalidateRelcache(relation);
+ relation->rd_node.relNode = relnode;
+ }
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
@@ -3664,7 +3733,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
- if (RelationIsMapped(relation))
+ else if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3710,9 +3779,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
}
- heap_freetuple(tuple);
+ if (update_catalog)
+ {
+ heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ table_close(pg_class, RowExclusiveLock);
+ }
/*
* Make the pg_class row change or relation map change visible. This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dabcbb0..5e3b5fc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
@@ -145,6 +146,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2036,6 +2049,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."),
@@ -2554,6 +2576,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+ NULL
+ },
+ &vacuum_gtt_defer_check_age,
+ 10000, 0, 1000000,
+ NULL, NULL, NULL
+ },
+
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3b36335..7a84997 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2432,6 +2432,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data in global temporary table/sequence */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -15618,6 +15622,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
char *ftoptions = NULL;
char *srvname = NULL;
char *foreign = "";
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15671,9 +15676,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);
@@ -16059,13 +16070,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/*
+ * Transaction information for the global temporary table is not stored
+ * in the pg_class.
+ */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(tbinfo->frozenxid == 0);
+ Assert(tbinfo->minmxid == 0);
+ }
+ /*
* In binary_upgrade mode, arrange to restore the old relfrozenxid and
* relminmxid of all vacuumable relations. (While vacuum.c processes
* TOAST tables semi-independently, here we see them only as children
* of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
* child toast table is handled below.)
*/
- if (dopt->binary_upgrade &&
+ else if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_MATVIEW))
{
@@ -16997,6 +17017,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer delqry = createPQExpBuffer();
char *qseqname;
+ bool global_temp_seq = false;
qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
@@ -17006,9 +17027,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
"SELECT format_type(seqtypid, NULL), "
"seqstart, seqincrement, "
"seqmax, seqmin, "
- "seqcache, seqcycle "
- "FROM pg_catalog.pg_sequence "
- "WHERE seqrelid = '%u'::oid",
+ "seqcache, seqcycle, "
+ "c.relpersistence "
+ "FROM pg_catalog.pg_sequence s, "
+ "pg_catalog.pg_class c "
+ "WHERE seqrelid = '%u'::oid "
+ "and s.seqrelid = c.oid",
tbinfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
@@ -17053,6 +17077,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
cache = PQgetvalue(res, 0, 5);
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ if (fout->remoteVersion >= 140000)
+ global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
/* Calculate default limits for a sequence of this type */
is_ascending = (incby[0] != '-');
if (strcmp(seqtype, "smallint") == 0)
@@ -17130,9 +17157,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
else
{
- appendPQExpBuffer(query,
- "CREATE SEQUENCE %s\n",
- fmtQualifiedDumpable(tbinfo));
+ appendPQExpBuffer(query, "CREATE ");
+
+ if (global_temp_seq)
+ appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+ appendPQExpBuffer(query, "SEQUENCE %s\n",
+ fmtQualifiedDumpable(tbinfo));
if (strcmp(seqtype, "bigint") != 0)
appendPQExpBuffer(query, " AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,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();
@@ -166,7 +166,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..5725cbe 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);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
+ CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+ if (skip_gtt)
+ {
+ /* exclude global temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+ }
+
/* exclude possible orphaned temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
" n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..32eccd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,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);
}
/*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,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 14150d0..b86a2a6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3756,7 +3756,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 3a43c09..ef0e6eb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,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},
@@ -2446,6 +2448,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" */
@@ -2654,6 +2659,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/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid);
+ MultiXactId *relminmxid,
+ bool skip_create_storage);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
#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 fc2202b..840e25c 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5590,6 +5590,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4388',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
/* GUC variables */
extern int wal_skip_threshold;
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..a2fadb0
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,47 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+ 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_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,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 af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e77f76a..7b8786d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -156,6 +156,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId backend_gtt_frozenxid; /* backend 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 ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_backend_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 6a20a3b..ed7d517 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,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 c5ffea4..6dda0b6 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
@@ -306,6 +306,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
@@ -571,11 +572,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.
*
@@ -583,6 +586,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ * Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+ (relpersistence == RELPERSISTENCE_TEMP || \
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep
+----------
+
+(1 row)
+
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR: views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+ 1 | 1
+ 2 | 3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2
+----+----
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 33 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
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..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+ gtt_stats | gtt_pkey | 1 | 0 | 0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+ gtt_stats | gtt_pkey | 30 | 10000 | 0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6293ab5..e362510 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relfilenode,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7..c498e39 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
st 9. 12. 2020 v 7:34 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com> napsal:
2020年12月8日 13:28,Pavel Stehule <pavel.stehule@gmail.com> 写道:
Hi
čt 26. 11. 2020 v 12:46 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com>
napsal:This is the latest patch for feature GTT(v38).
Everybody, if you have any questions, please let me know.please, co you send a rebased patch. It is broken again
Sure
This patch is base on 16c302f51235eaec05a1f85a11c1df04ef3a6785
Simplify code for getting a unicode codepoint's canonical class.
Thank you
Pavel
Show quoted text
Wenjing
Regards
Pavel
Wenjing
HI all
I added comments and refactored part of the implementation in Storage_gtt.c.
I hope this will help Reviewer.
Wenjing
Show quoted text
2020年12月9日 17:07,Pavel Stehule <pavel.stehule@gmail.com> 写道:
st 9. 12. 2020 v 7:34 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
2020年12月8日 13:28,Pavel Stehule <pavel.stehule@gmail.com <mailto:pavel.stehule@gmail.com>> 写道:
Hi
čt 26. 11. 2020 v 12:46 odesílatel 曾文旌 <wenjing.zwj@alibaba-inc.com <mailto:wenjing.zwj@alibaba-inc.com>> napsal:
This is the latest patch for feature GTT(v38).
Everybody, if you have any questions, please let me know.please, co you send a rebased patch. It is broken again
Sure
This patch is base on 16c302f51235eaec05a1f85a11c1df04ef3a6785
Simplify code for getting a unicode codepoint's canonical class.Thank you
Pavel
Wenjing
Regards
Pavel
Wenjing
Attachments:
global_temporary_table_v40-pg14.patchapplication/octet-stream; name=global_temporary_table_v40-pg14.patch; x-unix-mode=0644Download
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..9ea9a49 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 temporary table only
+ * use ShareUpdateExclusiveLock for ensure safety
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temporary table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1817,6 +1830,8 @@ bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)},
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
partitioned_table_reloptions(Datum reloptions, bool validate)
{
/*
- * There are no options for partitioned tables yet, but this is able to do
- * some validation.
+ * Add option for global temp partitioned tables.
*/
+ static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)}
+ };
+
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 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (RELATION_IS_TEMP(rel))
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* metapage, nor the first bitmap page.
*/
sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
- if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if (!RELATION_IS_TEMP(index))
sort_threshold = Min(sort_threshold, NBuffers);
else
sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3eea215..7c33841 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,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
@@ -642,7 +642,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 25f2d5d..e49b67b 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"
@@ -445,9 +446,13 @@ 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.
+ * Transaction information for the global temp table is stored in
+ * local hash, not catalog.
+ */
+ 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 793434c..8c76b66 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"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ /*
+ * If a global temporary table storage file is not initialized in the
+ * current session, its index does not have root page. just returns 0.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 8dd225c..852ca01 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6878,6 +6878,15 @@ StartupXLOG(void)
int rmid;
/*
+ * When pg backend processes crashes, those storage files that belong
+ * to global temporary tables will not be cleaned up.
+ * They will remain in the data directory. These data files no longer
+ * belong to any session and need to be cleaned up during the crash recovery.
+ */
+ if (max_active_gtt > 0)
+ RemovePgTempFiles();
+
+ /*
* Update pg_control to show that we are recovering and to show the
* selected checkpoint as the place we are starting from. We also mark
* pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
mapped_relation,
true,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..1d23b07
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global temporary table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global temporary Table .
+
+The metadata of Global temporary table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a global temporary table and writes some data.
+Other sessions cannot see those data, but they have an empty Global temporary
+table with same schema.
+
+Like local temporary table, global temporary table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global temporary table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for global temporary table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark global temp table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_subscription.o \
pg_type.o \
storage.o \
+ storage_gtt.o \
toasting.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 51b5c4f..d8d2ab7 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,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid)
+ MultiXactId *relminmxid,
+ bool skip_create_storage)
{
bool create_storage;
Relation rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
* storage is already created, so don't do it here. Also don't create it
* for relkinds without physical storage.
*/
- if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+ if (!RELKIND_HAS_STORAGE(relkind) ||
+ OidIsValid(relfilenode) ||
+ skip_create_storage)
create_storage = false;
else
{
@@ -427,7 +432,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:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
- new_rel_reltup->relfrozenxid = relfrozenxid;
- new_rel_reltup->relminmxid = relminmxid;
+ /*
+ * The transaction information of the global temporary table is stored
+ * in the local hash table, not 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;
@@ -1292,7 +1311,8 @@ heap_create_with_catalog(const char *relname,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1399,6 +1419,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1978,6 +1999,19 @@ heap_drop_with_catalog(Oid relid)
update_default_partition_oid(parentOid, InvalidOid);
/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3240,7 +3274,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;
@@ -3252,7 +3286,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
@@ -3298,8 +3332,16 @@ heap_truncate(List *relids)
{
Oid rid = lfirst_oid(cell);
Relation rel;
+ LOCKMODE lockmode = AccessExclusiveLock;
- rel = table_open(rid, AccessExclusiveLock);
+ /*
+ * Truncate global temporary table only clears local data,
+ * so only low-level locks need to be held.
+ */
+ if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3332,6 +3374,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3340,23 +3383,47 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ /*
+ * If this GTT is not initialized in current backend, there is
+ * no needs to anything.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(rel)))
+ return;
+
+ /*
+ * Truncate GTT only clears local data, so only low-level locks
+ * need to be held.
+ */
+ 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);
}
+
+ /*
+ * After the data is cleaned up on the GTT, the transaction information
+ * for the data(stored in local hash table) is also need reset.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..f5d4604 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
+ bool skip_create_storage = false;
+
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temporary table with concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex global temporary tables concurrently")));
+
+ /*
+ * For the case that some backend is applied relcache message to create
+ * an index on a global temporary table, if this table in the current
+ * backend are not initialized, the creation of index storage on the
+ * table are also skipped.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+ {
+ skip_create_storage = true;
+ flags |= INDEX_CREATE_SKIP_BUILD;
+ }
+ }
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ skip_create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
- Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
(!concurrent && !concurrent_lock_mode));
/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
+ * Allow to drop index on global temporary table when only current
+ * backend use it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+ is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop index %s or global temporary table %s",
+ RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+ errhint("Because the index is created on the global temporary table and other backend attached it.")));
+ }
+
+ /*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
HeapTuple tuple;
Form_pg_class rd_rel;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
/*
* We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
else /* don't bother for indexes */
relallvisible = 0;
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
+ /* For global temporary table */
+ if (is_gtt)
{
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
+ /* Update GTT'statistics into local relcache */
+ rel->rd_rel->relpages = (int32) relpages;
+ rel->rd_rel->reltuples = (float4) reltuples;
+ rel->rd_rel->relallvisible = (int32) relallvisible;
+
+ /* Update GTT'statistics into local hashtable */
+ up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+ InvalidTransactionId, InvalidMultiXactId);
}
- if (rd_rel->relallvisible != (int32) relallvisible)
+ else
{
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
+ if (rd_rel->relpages != (int32) relpages)
+ {
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
+
+ if (rd_rel->reltuples != (float4) reltuples)
+ {
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
+ }
+
+ if (rd_rel->relallvisible != (int32) relallvisible)
+ {
+ rd_rel->relallvisible = (int32) relallvisible;
+ dirty = true;
+ }
}
}
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
pgstat_progress_update_multi_param(6, index, val);
}
+ /* For build index on global temporary table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+ {
+ /*
+ * If the storage for the index in this session is not initialized,
+ * it needs to be created.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ /* Before create init storage, fix the local Relcache first */
+ force_enable_gtt_index(indexRelation);
+
+ Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+ /* Init storage for index */
+ RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+ }
+ }
+
/*
* Call the access method's build procedure
*/
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
if (!OidIsValid(heapId))
return;
+ /*
+ * For reindex on global temporary table, If the storage for the index
+ * in current backend is not initialized, nothing is done.
+ */
+ if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ !gtt_storage_attached(indexId))
+ {
+ /* Suppress use of the target index while rebuilding it */
+ SetReindexProcessing(heapId, indexId);
+ /* Re-allow use of target index */
+ ResetReindexProcessing();
+ return;
+ }
+
if ((options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
errmsg("cannot create temporary relation in non-temporary schema")));
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /* Do not allow create global temporary table in temporary schemas */
+ if (isAnyTempNamespace(nspid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create global temp table 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 d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
#include "access/xlogutils.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/freespace.h"
#include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
BackendId backend; /* InvalidBackendId if not a temp rel */
+ Oid temprelOid; /* InvalidOid if not a global temporary rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
* transaction aborts later on, the storage will be destroyed.
*/
SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
{
PendingRelDelete *pending;
SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
+ /*
+ * Global temporary table and local temporary table use same
+ * design on storage module.
+ */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rnode;
pending->backend = backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = false; /* delete if abort */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
AddPendingSync(&rnode);
}
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+ /*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /* Remember global temporary table storage info to localhash */
+ remember_gtt_storage_info(rnode, rel);
+ }
+
return srel;
}
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rel->rd_node;
pending->backend = rel->rd_backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = true; /* delete if commit */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
pendingDeletes = pending;
/*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /*
* NOTE: if the relation was created in this transaction, it will now be
* present in the pending-delete list twice, once with atCommit true and
* once with atCommit false. Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
+ Oid *reloids = NULL;
prev = NULL;
for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
+ reloids = palloc(sizeof(Oid) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ reloids = repalloc(reloids, sizeof(Oid) * maxrels);
}
- srels[nrels++] = srel;
+ srels[nrels] = srel;
+ reloids[nrels] = pending->temprelOid;
+ nrels++;
}
/* must explicitly free the list entry */
pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
if (nrels > 0)
{
+ int i;
+
smgrdounlinkall(srels, nrels, false);
- for (int i = 0; i < nrels; i++)
+ for (i = 0; i < nrels; i++)
+ {
smgrclose(srels[i]);
+ /* Delete global temporary table info in localhash */
+ if (gtt_storage_attached(reloids[i]))
+ forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+ }
+
pfree(srels);
+ pfree(reloids);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..3ff0c3a
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ * See src/backend/catalog/GTT_README for Global temparary table's
+ * requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/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_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+ LWLock lock;
+ int max_entry;
+ int entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+ Oid dbNode;
+ Oid relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+ gtt_fnode rnode;
+ Bitmapset *map;
+ /* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+ Oid relfilenode;
+ Oid spcnode;
+
+ /* pg_class relstat */
+ int32 relpages;
+ float4 reltuples;
+ int32 relallvisible;
+ TransactionId relfrozenxid;
+ TransactionId relminmxid;
+
+ /* pg_statistic column stat */
+ int natts;
+ int *attnum;
+ HeapTuple *att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+ Oid relid;
+
+ List *relfilenode_list;
+
+ char relkind;
+ bool on_commit_delete;
+
+ Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+ int wordnum;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ wordnum = WORDNUM(MaxBackends + 1);
+ /* hash entry header size */
+ hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+ /*
+ * hash entry data size
+ * this is a bitmap in shared memory, each backend have a bit.
+ */
+ hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+ return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+ Size size = 0;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ /* shared hash header size */
+ size = MAXALIGN(sizeof(gtt_ctl_data));
+ /* hash entry size */
+ hash_entry_size = action_gtt_shared_hash_entry_size();
+ /* max size */
+ size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+ return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+ HASHCTL info;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ gtt_shared_ctl =
+ ShmemInitStruct("gtt_shared_ctl",
+ sizeof(gtt_ctl_data),
+ &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+ LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (!found)
+ {
+ int wordnum;
+
+ /* init bitmap */
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ /* record itself in bitmap */
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* remove itself from bitmap */
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+
+ /* copy the entire bitmap */
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* how many backend are using this GTT */
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ /* check if this is itself */
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ /* First time through: initialize the hash table */
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ HASHCTL ctl;
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_info_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt info context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ ctl.hcxt = gtt_info_context;
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ }
+
+ Assert(CacheMemoryContext);
+ Assert(gtt_info_context);
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->oldrelid = InvalidOid;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ /* record the on commit clause */
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ /* record storage info relstat columnstats and transaction info to relfilenode list */
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ new_node->natts = 0;
+ new_node->attnum = NULL;
+ new_node->att_stat_tups = NULL;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* init column stats structure */
+ natts = RelationGetNumberOfAttributes(rel);
+ new_node->attnum = palloc0(sizeof(int) * natts);
+ new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ new_node->natts = natts;
+
+ /* only heap have transaction info */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+
+ /**/
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Registration callbacks are used to trigger cleanup during process exit */
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ /*
+ * For cluster GTT rollback.
+ * We need to roll back the exchange relfilenode operation.
+ */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+
+ /* temp relfilenode need free */
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ /* rollback transaction */
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ /* Clean up transaction info from Local order list and MyProc */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+ /* this is valid relfrozenxid */
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ /* delete relfilenode from rel entry */
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ gtt_free_statistics(d_rnode);
+
+ if (entry->relfilenode_list == NIL)
+ {
+ /* this means we truncate this GTT at current backend */
+
+ /* tell shared hash that current backend will no longer use this GTT */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ /* commit transaction at cluster GTT, need clean up footprint */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ entry2->oldrelid = InvalidOid;
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ /* Search all relfilenode for GTT in current backend */
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ /* drop local buffer and storage */
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ /* tell shared hash */
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ /* set to global area */
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ /* set to local order list */
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ /* set to global area */
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ Assert(entry->relid == reloid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (gtt_rnode->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ /* switch context to gtt_info_context for store tuple at heap_form_tuple */
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == 0)
+ {
+ gtt_rnode->attnum[i] = attnum;
+ break;
+ }
+ else if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ heap_freetuple(gtt_rnode->att_stat_tups[i]);
+ gtt_rnode->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < gtt_rnode->natts);
+ Assert(gtt_rnode->att_stat_tups[i] == NULL);
+ gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return NULL;
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ return gtt_rnode->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ HeapTuple tuple;
+ Relation rel = NULL;
+ Oid reloid = PG_GETARG_OID(0);
+ int attnum = PG_GETARG_INT32(1);
+ char rel_persistence;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ /* get data from local hash */
+ 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);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ Oid relnode = 0;
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ double reltuples = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ /* get data from share hash */
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ /* backendid map to process pid */
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ 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);
+
+ /* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+ oldest = list_all_backend_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;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be GTT */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this GTT is not initialized in the current backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ /* Each GTT is initialized once in each backend */
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ /* init heap storage */
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ 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);
+
+ /* init index storage */
+ 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);
+ /* build empty index */
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+ int i;
+
+ Assert(rnode);
+
+ for (i = 0; i < rnode->natts; i++)
+ {
+ if (rnode->att_stat_tups[i])
+ {
+ heap_freetuple(rnode->att_stat_tups[i]);
+ rnode->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (rnode->attnum)
+ pfree(rnode->attnum);
+
+ if (rnode->att_stat_tups)
+ pfree(rnode->att_stat_tups);
+
+ pfree(rnode);
+
+ return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c21..6624fc8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 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);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not 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 fd5a6ee..42c228f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -389,6 +396,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -772,6 +791,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -785,6 +807,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -852,20 +877,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -933,6 +976,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1368,10 +1420,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1579,3 +1643,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 14d24b3..93490fb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -561,7 +561,7 @@ DefineIndex(Oid relationId,
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
- if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
concurrent = true;
else
concurrent = false;
@@ -2528,7 +2528,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(indOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(persistence))
ReindexRelationConcurrently(indOid, options);
else
reindex_index(indOid, false, persistence,
@@ -2633,7 +2633,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
{
result = ReindexRelationConcurrently(heapOid, options);
@@ -2992,7 +2992,7 @@ ReindexMultipleInternal(List *relids, int options)
relkind != RELKIND_PARTITIONED_TABLE);
if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- relpersistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(relpersistence))
{
(void) ReindexRelationConcurrently(relid,
options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (get_rel_relkind(reloid) == RELKIND_VIEW)
+ /* Lock table command ignores global temporary table. */
+ if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+ else if (get_rel_relkind(reloid) == RELKIND_VIEW)
LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
else if (recurse)
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fa2eea8..3db348d 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"
@@ -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);
/*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqdatatuple;
HeapTuple tuple;
- HeapTuple pgstuple;
- Form_pg_sequence pgsform;
int64 startv;
/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
- pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
- if (!HeapTupleIsValid(pgstuple))
- elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
- pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
- startv = pgsform->seqstart;
- ReleaseSysCache(pgstuple);
+ startv = get_seqence_start_value(seq_relid);
/*
* Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
init_sequence(relid, &elm, &seqrel);
+ if (RELATION_IS_GLOBAL_TEMP(seqrel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+ RelationGetRelationName(seqrel))));
+ }
+
rel = table_open(SequenceRelationId, RowExclusiveLock);
seqtuple = SearchSysCacheCopy1(SEQRELID,
ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ /* Initializes the storage for sequence which the global temporary table belongs. */
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(RelationGetRelid(seqrel)))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+ 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);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19..c2740cc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -45,6 +45,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"
@@ -559,6 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -604,6 +606,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
@@ -615,7 +618,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !RelpersistenceTsTemp(stmt->relation->relpersistence))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -645,7 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if (RelpersistenceTsTemp(stmt->relation->relpersistence)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -746,6 +749,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* For global temporary table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (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);
+ }
+
+ /* Check oncommit clause and save to reloptions */
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temporary 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 temporary table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1367,7 +1420,7 @@ RemoveRelations(DropStmt *drop)
* relation persistence cannot be known without its OID.
*/
if (drop->concurrent &&
- get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(relOid)))
{
Assert(list_length(drop->objects) == 1 &&
drop->removeType == OBJECT_INDEX);
@@ -1573,7 +1626,16 @@ ExecuteTruncate(TruncateStmt *stmt)
Relation rel;
bool recurse = rv->inh;
Oid myrelid;
- LOCKMODE lockmode = AccessExclusiveLock;
+ LOCKMODE lockmode;
+
+ /*
+ * Truncate global temp table only cleans up the data in current backend,
+ * only low-level locks are required.
+ */
+ if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+ else
+ lockmode = AccessExclusiveLock;
myrelid = RangeVarGetRelidExtended(rv, lockmode,
0, RangeVarCallbackForTruncate,
@@ -1838,6 +1900,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
continue;
/*
+ * Skip the global temporary table that is not initialized for storage
+ * in current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ continue;
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -3681,6 +3751,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temporary table only current backend use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5004,6 +5084,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ {
+ if (tab->chgPersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change global temporary table persistence setting")));
+
+ /*
+ * The storage for the global temporary table needs to be initialized
+ * before rewrite table.
+ */
+ if(!gtt_storage_attached(tab->relid))
+ {
+ ResultRelInfo *resultRelInfo;
+ MemoryContext oldcontext;
+ MemoryContext ctx_alter_gtt;
+
+ ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+ "gtt alter table", ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, OldHeap,
+ 1, NULL, 0);
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
+ init_gtt_storage(CMD_UTILITY, resultRelInfo);
+ ExecCloseIndices(resultRelInfo);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(ctx_alter_gtt);
+ }
+ }
+
/*
* Select destination tablespace (same as original unless user
* requested a change)
@@ -8468,6 +8584,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;
}
/*
@@ -12971,6 +13093,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -13173,6 +13298,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 temporary table");
+
/*
* No work if no change in tablespace.
*/
@@ -13547,7 +13675,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,
@@ -14955,6 +15083,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17631,3 +17760,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
CacheInvalidateRelcache(rel);
}
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ bool res = false;
+ char *sval = defGetString(def);
+
+ /* It has to be a Boolean value */
+ if (!parse_bool(sval, &res))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+ if (res)
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 98270a1..66533d6 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"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+ /* For global temporary table */
+ if (is_gtt)
+ {
+ /* Store relation statistics and transaction information to the localhash */
+ up_gtt_relstats(RelationGetRelid(relation),
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+
+ /* Update relation statistics to local relcache */
+ relation->rd_rel->relpages = (int32) num_pages;
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (!is_gtt &&
+ pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (!is_gtt &&
+ pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (!is_gtt &&
+ pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1301,7 +1324,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(),
@@ -1312,7 +1336,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)))
@@ -1421,6 +1446,13 @@ vac_update_datfrozenxid(void)
}
/*
+ * The relfrozenxid for a global temporary talble is stored in localhash,
+ * not pg_class, See list_all_session_gtt_frozenxids()
+ */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
+ /*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
* to not be set (i.e. set to their respective Invalid*Id)
@@ -1477,6 +1509,43 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /* If enable global temporary table */
+ if (max_active_gtt > 0)
+ {
+ TransactionId safe_age;
+ /* */
+ TransactionId oldest_gtt_frozenxid =
+ list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+ {
+ safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+ if (safe_age < FirstNormalTransactionId)
+ safe_age += FirstNormalTransactionId;
+
+ /*
+ * We tolerate that the minimum age of gtt is less than
+ * the minimum age of conventional tables, otherwise it will
+ * throw warning message.
+ */
+ if (TransactionIdIsNormal(safe_age) &&
+ TransactionIdPrecedes(safe_age, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+ oldest_gtt_frozenxid),
+ errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+ errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+ }
+
+ /*
+ * We need to ensure that the clog required by gtt is not cleand.
+ */
+ if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1829,6 +1898,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
}
/*
+ * Skip those global temporary table that are not initialized in
+ * current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(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
* contents are probably not up-to-date on disk. (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("views cannot be unlogged because they do not have storage")));
+ /* Global temporary table are not sensible. */
+ if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("views cannot be global temp because they do not have storage")));
+
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly. We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ /* This is one kind of temp table */
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 97bfc8b..ccf6e09 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"
@@ -605,6 +606,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ /* Init storage for partitioned global temporary table in current backend */
+ 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 ab3d655..bb7b86b 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"
@@ -2286,6 +2287,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ /* Init storage for global temporary table in current backend */
+ 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 84a69b0..e6b4b0a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58..df46f36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6430,7 +6430,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
- if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ if (RELATION_IS_TEMP(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index daf1759..36475df 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/heap.h"
#include "catalog/index.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -229,6 +230,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temporary table in current backend */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,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 8f341ac..48c399c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3311,17 +3311,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; }
@@ -11394,19 +11388,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 a56bd86..702bb67 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);
/*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+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 temporary 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 89ee990..6e61a41 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->options = seqoptions;
/*
+ * If a sequence is bound to a global temporary table, then the sequence
+ * must been "global temporary"
+ */
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+ /*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
* clause, the "redundant options" error will point to their occurrence,
@@ -3222,6 +3229,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isforeign = false;
}
cxt.relation = stmt->relation;
+ /* Sets the table persistence to the context */
+ cxt.relation->relpersistence = RelationGetRelPersistence(rel);
cxt.rel = rel;
cxt.inhRelations = NIL;
cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index ed127a1..368b164 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /*
+ * Aotuvacuum cannot vacuum the private data stored in each backend
+ * that belongs to global temporary table, so skip them.
+ */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(classForm->relpersistence))
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index c5e8707..ae3cdfb 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"
@@ -2848,6 +2849,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * Returns 0 if this global temporary table is not initialized in current
+ * backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(RelationGetRelid(relation)))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
#include "access/subtrans.h"
#include "access/syncscan.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -149,6 +150,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
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* For global temporary table shared hashtable */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index ee912b9..299ed04 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -5037,3 +5038,78 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ uint8 flags = 0;
+ int i = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ /* Disable in standby node */
+ 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];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+
+ if (statusFlags & flags)
+ continue;
+
+ /* Fetch all backend that is belonging to MyDatabaseId */
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->backend_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+ result = proc->backend_gtt_frozenxid;
+
+ /* save backend pid and backend level oldest relfrozenxid */
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->backend_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 26bcce9..9e6c01a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
/* LWTRANCHE_PARALLEL_APPEND: */
"ParallelAppend",
/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
- "PerXactPredicateList"
+ "PerXactPredicateList",
+ /* LWTRANCHE_GTT_CTL */
+ "GlobalTempTableControl"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7dc3911..def8956 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..db1eb07 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /*
+ * For global temporary table ,each backend has its own storage,
+ * also only sees its own storage. Use Backendid to identify them.
+ */
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 80bd60f..98b6196 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6450,6 +6478,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;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,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;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,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))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7435,6 +7493,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.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 204bcd0..1f598c8 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -3076,6 +3077,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3bd5e18..ddcbb78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,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"
@@ -1135,6 +1136,28 @@ 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;
+
+ /* For global temporary table, get relstat data from localhash */
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+
+ /* And put them to local relcache */
+ 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);
@@ -1189,6 +1212,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -1313,7 +1338,22 @@ RelationInitPhysicalAddr(Relation relation)
heap_freetuple(phys_tuple);
}
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ /*
+ * For global temporary table, get the latest relfilenode
+ * from localhash and put it in relcache.
+ */
+ if (OidIsValid(newrelnode) &&
+ newrelnode != relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = newrelnode;
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ }
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
}
else
{
@@ -2254,6 +2294,9 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
}
/* Okay, now it's valid again */
@@ -3480,6 +3523,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;
@@ -3587,28 +3634,38 @@ void
RelationSetNewRelfilenode(Relation relation, char persistence)
{
Oid newrelfilenode;
- Relation pg_class;
- HeapTuple tuple;
+ Relation pg_class = NULL;
+ HeapTuple tuple = NULL;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ /*
+ * For global temporary table, storage information for the table is
+ * maintained locally, not in catalog.
+ */
+ bool update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
- /*
- * Get a writable copy of the pg_class tuple for the given relation.
- */
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ memset(&classform, 0, sizeof(classform));
- tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u",
- RelationGetRelid(relation));
- classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (update_catalog)
+ {
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ }
/*
* Schedule unlinking of the old storage at transaction commit.
@@ -3634,7 +3691,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;
@@ -3654,6 +3711,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
break;
}
+ /* For global temporary table */
+ if (!update_catalog)
+ {
+ Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ Assert(RELATION_IS_GLOBAL_TEMP(relation));
+ Assert(!RelationIsMapped(relation));
+
+ /* Make cache invalid and set new relnode to local cache. */
+ CacheInvalidateRelcache(relation);
+ relation->rd_node.relNode = relnode;
+ }
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
@@ -3663,7 +3732,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
- if (RelationIsMapped(relation))
+ else if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3709,9 +3778,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
}
- heap_freetuple(tuple);
+ if (update_catalog)
+ {
+ heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ table_close(pg_class, RowExclusiveLock);
+ }
/*
* Make the pg_class row change or relation map change visible. This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dabcbb0..5e3b5fc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
@@ -145,6 +146,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2036,6 +2049,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."),
@@ -2554,6 +2576,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+ NULL
+ },
+ &vacuum_gtt_defer_check_age,
+ 10000, 0, 1000000,
+ NULL, NULL, NULL
+ },
+
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 673a670..81a39a9 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2432,6 +2432,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data in global temporary table/sequence */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -15633,6 +15637,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
char *ftoptions = NULL;
char *srvname = NULL;
char *foreign = "";
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15686,9 +15691,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);
@@ -16074,13 +16085,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/*
+ * Transaction information for the global temporary table is not stored
+ * in the pg_class.
+ */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(tbinfo->frozenxid == 0);
+ Assert(tbinfo->minmxid == 0);
+ }
+ /*
* In binary_upgrade mode, arrange to restore the old relfrozenxid and
* relminmxid of all vacuumable relations. (While vacuum.c processes
* TOAST tables semi-independently, here we see them only as children
* of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
* child toast table is handled below.)
*/
- if (dopt->binary_upgrade &&
+ else if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_MATVIEW))
{
@@ -17012,6 +17032,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer delqry = createPQExpBuffer();
char *qseqname;
+ bool global_temp_seq = false;
qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
@@ -17021,9 +17042,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
"SELECT format_type(seqtypid, NULL), "
"seqstart, seqincrement, "
"seqmax, seqmin, "
- "seqcache, seqcycle "
- "FROM pg_catalog.pg_sequence "
- "WHERE seqrelid = '%u'::oid",
+ "seqcache, seqcycle, "
+ "c.relpersistence "
+ "FROM pg_catalog.pg_sequence s, "
+ "pg_catalog.pg_class c "
+ "WHERE seqrelid = '%u'::oid "
+ "and s.seqrelid = c.oid",
tbinfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
@@ -17068,6 +17092,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
cache = PQgetvalue(res, 0, 5);
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ if (fout->remoteVersion >= 140000)
+ global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
/* Calculate default limits for a sequence of this type */
is_ascending = (incby[0] != '-');
if (strcmp(seqtype, "smallint") == 0)
@@ -17145,9 +17172,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
else
{
- appendPQExpBuffer(query,
- "CREATE SEQUENCE %s\n",
- fmtQualifiedDumpable(tbinfo));
+ appendPQExpBuffer(query, "CREATE ");
+
+ if (global_temp_seq)
+ appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+ appendPQExpBuffer(query, "SEQUENCE %s\n",
+ fmtQualifiedDumpable(tbinfo));
if (strcmp(seqtype, "bigint") != 0)
appendPQExpBuffer(query, " AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,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();
@@ -166,7 +166,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..5725cbe 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);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
+ CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+ if (skip_gtt)
+ {
+ /* exclude global temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+ }
+
/* exclude possible orphaned temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
" n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..32eccd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,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);
}
/*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,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 14150d0..b86a2a6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3756,7 +3756,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 3a43c09..ef0e6eb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,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},
@@ -2446,6 +2448,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" */
@@ -2654,6 +2659,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/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid);
+ MultiXactId *relminmxid,
+ bool skip_create_storage);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
#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 e6c7b07..062aea9 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5590,6 +5590,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4388',
+ descr => 'List local statistics for global temporary table',
+ proname => 'pg_get_gtt_statistics', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid int4 anyelement',
+ proallargtypes => '{oid,int4,anyelement,oid,int2,bool,float4,int4,float4,int2,int2,int2,int2,int2,oid,oid,oid,oid,oid,oid,oid,oid,oid,oid,_float4,_float4,_float4,_float4,_float4,anyarray,anyarray,anyarray,anyarray,anyarray}',
+ proargmodes => '{i,i,i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{relid,att,x,starelid,staattnum,stainherit,stanullfrac,stawidth,stadistinct,stakind1,stakind2,stakind3,stakind4,stakind5,staop1,staop2,staop3,staop4,staop5,stacoll1,stacoll2,stacoll3,stacoll4,stacoll5,stanumbers1,stanumbers2,stanumbers3,stanumbers4,stanumbers5,stavalues1,stavalues2,stavalues3,stavalues4,stavalues5}',
+ prosrc => 'pg_get_gtt_statistics' },
+{ oid => '4389',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4390',
+ descr => 'List attached pid for one global temporary table',
+ proname => 'pg_gtt_attached_pid', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4}',
+ proargmodes => '{i,o,o}',
+ proargnames => '{relid,relid,pid}',
+ prosrc => 'pg_gtt_attached_pid' },
+{ oid => '4391',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
/* GUC variables */
extern int wal_skip_threshold;
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..d48162c
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+ 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 force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,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 af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e77f76a..7b8786d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -156,6 +156,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId backend_gtt_frozenxid; /* backend 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 ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_backend_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 6a20a3b..ed7d517 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,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 c5ffea4..6dda0b6 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
@@ -306,6 +306,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
@@ -571,11 +572,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.
*
@@ -583,6 +586,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ * Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+ (relpersistence == RELPERSISTENCE_TEMP || \
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep
+----------
+
+(1 row)
+
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR: views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+ 1 | 1
+ 2 | 3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2
+----+----
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 33 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
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..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+ gtt_stats | gtt_pkey | 1 | 0 | 0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+ gtt_stats | gtt_pkey | 30 | 10000 | 0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6293ab5..e362510 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relfilenode,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index ae89ed7..c498e39 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
HI all
I rebased patch, fix OID conflicts, fix some comments.
Code updates to v41.
Wenjing
Attachments:
global_temporary_table_v41-pg14.patchapplication/octet-stream; name=global_temporary_table_v41-pg14.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index 8ccc228..1f9c4c9 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
},
true
},
+ /*
+ * In order to avoid consistency problems, the global temporary table
+ * uses ShareUpdateExclusiveLock.
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temporary table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1817,6 +1830,8 @@ bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)},
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
partitioned_table_reloptions(Datum reloptions, bool validate)
{
/*
- * There are no options for partitioned tables yet, but this is able to do
- * some validation.
+ * Add option for global temp partitioned tables.
*/
+ static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)}
+ };
+
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 615b5ad..7736799 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (RELATION_IS_TEMP(rel))
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 7c9ccf4..f4561bc 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* metapage, nor the first bitmap page.
*/
sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
- if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if (!RELATION_IS_TEMP(index))
sort_threshold = Min(sort_threshold, NBuffers);
else
sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 3eea215..7c33841 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,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
@@ -642,7 +642,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 25f2d5d..f33b3d5 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"
@@ -445,9 +446,13 @@ 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.
+ * Transaction information for the global temp table will be stored
+ * in the local hash table, not the catalog.
+ */
+ 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 793434c..dd96a51 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"
@@ -609,6 +610,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ /*
+ * If a global temporary table storage file is not initialized in the
+ * current backend, its index does not have a root page, just returns 0.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index b1e5d2d..1d90b24 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6889,6 +6889,16 @@ StartupXLOG(void)
int rmid;
/*
+ * When some backend processes crash, those storage files that belong to
+ * global temporary tables will not be cleaned up and remain in the data
+ * directory.
+ * These data files no longer belong to any session and will be cleaned up
+ * during the recovery.
+ */
+ if (max_active_gtt > 0)
+ RemovePgTempFiles();
+
+ /*
* Update pg_control to show that we are recovering and to show the
* selected checkpoint as the place we are starting from. We also mark
* pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 6bb0c6e..9ad28c4 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
mapped_relation,
true,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..3a0ec50
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 2519771..e31d817 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_subscription.o \
pg_type.o \
storage.o \
+ storage_gtt.o \
toasting.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index f984514..106e22e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 51b5c4f..d8d2ab7 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,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid)
+ MultiXactId *relminmxid,
+ bool skip_create_storage)
{
bool create_storage;
Relation rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
* storage is already created, so don't do it here. Also don't create it
* for relkinds without physical storage.
*/
- if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+ if (!RELKIND_HAS_STORAGE(relkind) ||
+ OidIsValid(relfilenode) ||
+ skip_create_storage)
create_storage = false;
else
{
@@ -427,7 +432,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:
@@ -988,6 +993,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -1026,8 +1032,21 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
- new_rel_reltup->relfrozenxid = relfrozenxid;
- new_rel_reltup->relminmxid = relminmxid;
+ /*
+ * The transaction information of the global temporary table is stored
+ * in the local hash table, not 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;
@@ -1292,7 +1311,8 @@ heap_create_with_catalog(const char *relname,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1399,6 +1419,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1978,6 +1999,19 @@ heap_drop_with_catalog(Oid relid)
update_default_partition_oid(parentOid, InvalidOid);
/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3240,7 +3274,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;
@@ -3252,7 +3286,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
@@ -3298,8 +3332,16 @@ heap_truncate(List *relids)
{
Oid rid = lfirst_oid(cell);
Relation rel;
+ LOCKMODE lockmode = AccessExclusiveLock;
- rel = table_open(rid, AccessExclusiveLock);
+ /*
+ * Truncate global temporary table only clears local data,
+ * so only low-level locks need to be held.
+ */
+ if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3332,6 +3374,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3340,23 +3383,47 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ /*
+ * If this GTT is not initialized in current backend, there is
+ * no needs to anything.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(rel)))
+ return;
+
+ /*
+ * Truncate GTT only clears local data, so only low-level locks
+ * need to be held.
+ */
+ 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);
}
+
+ /*
+ * After the data is cleaned up on the GTT, the transaction information
+ * for the data(stored in local hash table) is also need reset.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 731610c..f5d4604 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
+ bool skip_create_storage = false;
+
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temporary table with concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex global temporary tables concurrently")));
+
+ /*
+ * For the case that some backend is applied relcache message to create
+ * an index on a global temporary table, if this table in the current
+ * backend are not initialized, the creation of index storage on the
+ * table are also skipped.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+ {
+ skip_create_storage = true;
+ flags |= INDEX_CREATE_SKIP_BUILD;
+ }
+ }
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ skip_create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
- Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
(!concurrent && !concurrent_lock_mode));
/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
+ * Allow to drop index on global temporary table when only current
+ * backend use it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+ is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop index %s or global temporary table %s",
+ RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+ errhint("Because the index is created on the global temporary table and other backend attached it.")));
+ }
+
+ /*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
HeapTuple tuple;
Form_pg_class rd_rel;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
/*
* We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
else /* don't bother for indexes */
relallvisible = 0;
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
+ /* For global temporary table */
+ if (is_gtt)
{
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
+ /* Update GTT'statistics into local relcache */
+ rel->rd_rel->relpages = (int32) relpages;
+ rel->rd_rel->reltuples = (float4) reltuples;
+ rel->rd_rel->relallvisible = (int32) relallvisible;
+
+ /* Update GTT'statistics into local hashtable */
+ up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+ InvalidTransactionId, InvalidMultiXactId);
}
- if (rd_rel->relallvisible != (int32) relallvisible)
+ else
{
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
+ if (rd_rel->relpages != (int32) relpages)
+ {
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
+
+ if (rd_rel->reltuples != (float4) reltuples)
+ {
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
+ }
+
+ if (rd_rel->relallvisible != (int32) relallvisible)
+ {
+ rd_rel->relallvisible = (int32) relallvisible;
+ dirty = true;
+ }
}
}
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
pgstat_progress_update_multi_param(6, index, val);
}
+ /* For build index on global temporary table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+ {
+ /*
+ * If the storage for the index in this session is not initialized,
+ * it needs to be created.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ /* Before create init storage, fix the local Relcache first */
+ force_enable_gtt_index(indexRelation);
+
+ Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+ /* Init storage for index */
+ RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+ }
+ }
+
/*
* Call the access method's build procedure
*/
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
if (!OidIsValid(heapId))
return;
+ /*
+ * For reindex on global temporary table, If the storage for the index
+ * in current backend is not initialized, nothing is done.
+ */
+ if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ !gtt_storage_attached(indexId))
+ {
+ /* Suppress use of the target index while rebuilding it */
+ SetReindexProcessing(heapId, indexId);
+ /* Re-allow use of target index */
+ ResetReindexProcessing();
+ return;
+ }
+
if ((options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 740570c..bcd24d9 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
errmsg("cannot create temporary relation in non-temporary schema")));
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /* Do not allow create global temporary table in temporary schemas */
+ if (isAnyTempNamespace(nspid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create global temp table 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 d538f25..dbc84e8 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
#include "access/xlogutils.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/freespace.h"
#include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
BackendId backend; /* InvalidBackendId if not a temp rel */
+ Oid temprelOid; /* InvalidOid if not a global temporary rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
* transaction aborts later on, the storage will be destroyed.
*/
SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
{
PendingRelDelete *pending;
SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
+ /*
+ * Global temporary table and local temporary table use same
+ * design on storage module.
+ */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rnode;
pending->backend = backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = false; /* delete if abort */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
AddPendingSync(&rnode);
}
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+ /*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /* Remember global temporary table storage info to localhash */
+ remember_gtt_storage_info(rnode, rel);
+ }
+
return srel;
}
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rel->rd_node;
pending->backend = rel->rd_backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = true; /* delete if commit */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
pendingDeletes = pending;
/*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /*
* NOTE: if the relation was created in this transaction, it will now be
* present in the pending-delete list twice, once with atCommit true and
* once with atCommit false. Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
+ Oid *reloids = NULL;
prev = NULL;
for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
+ reloids = palloc(sizeof(Oid) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ reloids = repalloc(reloids, sizeof(Oid) * maxrels);
}
- srels[nrels++] = srel;
+ srels[nrels] = srel;
+ reloids[nrels] = pending->temprelOid;
+ nrels++;
}
/* must explicitly free the list entry */
pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
if (nrels > 0)
{
+ int i;
+
smgrdounlinkall(srels, nrels, false);
- for (int i = 0; i < nrels; i++)
+ for (i = 0; i < nrels; i++)
+ {
smgrclose(srels[i]);
+ /* Delete global temporary table info in localhash */
+ if (gtt_storage_attached(reloids[i]))
+ forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+ }
+
pfree(srels);
+ pfree(reloids);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..3ff0c3a
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ * See src/backend/catalog/GTT_README for Global temparary table's
+ * requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/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_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+ LWLock lock;
+ int max_entry;
+ int entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+ Oid dbNode;
+ Oid relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+ gtt_fnode rnode;
+ Bitmapset *map;
+ /* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+ Oid relfilenode;
+ Oid spcnode;
+
+ /* pg_class relstat */
+ int32 relpages;
+ float4 reltuples;
+ int32 relallvisible;
+ TransactionId relfrozenxid;
+ TransactionId relminmxid;
+
+ /* pg_statistic column stat */
+ int natts;
+ int *attnum;
+ HeapTuple *att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+ Oid relid;
+
+ List *relfilenode_list;
+
+ char relkind;
+ bool on_commit_delete;
+
+ Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+ int wordnum;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ wordnum = WORDNUM(MaxBackends + 1);
+ /* hash entry header size */
+ hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+ /*
+ * hash entry data size
+ * this is a bitmap in shared memory, each backend have a bit.
+ */
+ hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+ return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+ Size size = 0;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ /* shared hash header size */
+ size = MAXALIGN(sizeof(gtt_ctl_data));
+ /* hash entry size */
+ hash_entry_size = action_gtt_shared_hash_entry_size();
+ /* max size */
+ size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+ return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+ HASHCTL info;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ gtt_shared_ctl =
+ ShmemInitStruct("gtt_shared_ctl",
+ sizeof(gtt_ctl_data),
+ &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+ LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (!found)
+ {
+ int wordnum;
+
+ /* init bitmap */
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ /* record itself in bitmap */
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* remove itself from bitmap */
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+
+ /* copy the entire bitmap */
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* how many backend are using this GTT */
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ /* check if this is itself */
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ /* First time through: initialize the hash table */
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ HASHCTL ctl;
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_info_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt info context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ ctl.hcxt = gtt_info_context;
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ }
+
+ Assert(CacheMemoryContext);
+ Assert(gtt_info_context);
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->oldrelid = InvalidOid;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ /* record the on commit clause */
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ /* record storage info relstat columnstats and transaction info to relfilenode list */
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ new_node->natts = 0;
+ new_node->attnum = NULL;
+ new_node->att_stat_tups = NULL;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* init column stats structure */
+ natts = RelationGetNumberOfAttributes(rel);
+ new_node->attnum = palloc0(sizeof(int) * natts);
+ new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ new_node->natts = natts;
+
+ /* only heap have transaction info */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+
+ /**/
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Registration callbacks are used to trigger cleanup during process exit */
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ /*
+ * For cluster GTT rollback.
+ * We need to roll back the exchange relfilenode operation.
+ */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+
+ /* temp relfilenode need free */
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ /* rollback transaction */
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ /* Clean up transaction info from Local order list and MyProc */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+ /* this is valid relfrozenxid */
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ /* delete relfilenode from rel entry */
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ gtt_free_statistics(d_rnode);
+
+ if (entry->relfilenode_list == NIL)
+ {
+ /* this means we truncate this GTT at current backend */
+
+ /* tell shared hash that current backend will no longer use this GTT */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ /* commit transaction at cluster GTT, need clean up footprint */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ entry2->oldrelid = InvalidOid;
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ /* Search all relfilenode for GTT in current backend */
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ /* drop local buffer and storage */
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ /* tell shared hash */
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ /* set to global area */
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ /* set to local order list */
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ /* set to global area */
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ Assert(entry->relid == reloid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (gtt_rnode->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ /* switch context to gtt_info_context for store tuple at heap_form_tuple */
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == 0)
+ {
+ gtt_rnode->attnum[i] = attnum;
+ break;
+ }
+ else if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ heap_freetuple(gtt_rnode->att_stat_tups[i]);
+ gtt_rnode->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < gtt_rnode->natts);
+ Assert(gtt_rnode->att_stat_tups[i] == NULL);
+ gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return NULL;
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ return gtt_rnode->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ HeapTuple tuple;
+ Relation rel = NULL;
+ Oid reloid = PG_GETARG_OID(0);
+ int attnum = PG_GETARG_INT32(1);
+ char rel_persistence;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ /* get data from local hash */
+ 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);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ Oid relnode = 0;
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ double reltuples = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ /* get data from share hash */
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ /* backendid map to process pid */
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ 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);
+
+ /* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+ oldest = list_all_backend_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;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be GTT */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this GTT is not initialized in the current backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ /* Each GTT is initialized once in each backend */
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ /* init heap storage */
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ 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);
+
+ /* init index storage */
+ 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);
+ /* build empty index */
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+ int i;
+
+ Assert(rnode);
+
+ for (i = 0; i < rnode->natts; i++)
+ {
+ if (rnode->att_stat_tups[i])
+ {
+ heap_freetuple(rnode->att_stat_tups[i]);
+ rnode->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (rnode->attnum)
+ pfree(rnode->attnum);
+
+ if (rnode->att_stat_tups)
+ pfree(rnode->att_stat_tups);
+
+ pfree(rnode);
+
+ return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index b140c21..6624fc8 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 8af12b5..a0b6390 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);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not 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 fd5a6ee..42c228f 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -389,6 +396,18 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -772,6 +791,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -785,6 +807,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -852,20 +877,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -933,6 +976,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1368,10 +1420,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1579,3 +1643,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index b6143b8..e8da86c 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index 1b14e9a..5e918f2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/trigger.h"
@@ -655,6 +656,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 14d24b3..93490fb 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -561,7 +561,7 @@ DefineIndex(Oid relationId,
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
- if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
concurrent = true;
else
concurrent = false;
@@ -2528,7 +2528,7 @@ ReindexIndex(RangeVar *indexRelation, int options, bool isTopLevel)
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(indOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(persistence))
ReindexRelationConcurrently(indOid, options);
else
reindex_index(indOid, false, persistence,
@@ -2633,7 +2633,7 @@ ReindexTable(RangeVar *relation, int options, bool isTopLevel)
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, options, isTopLevel);
else if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
{
result = ReindexRelationConcurrently(heapOid, options);
@@ -2992,7 +2992,7 @@ ReindexMultipleInternal(List *relids, int options)
relkind != RELKIND_PARTITIONED_TABLE);
if ((options & REINDEXOPT_CONCURRENTLY) != 0 &&
- relpersistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(relpersistence))
{
(void) ReindexRelationConcurrently(relid,
options |
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 0982276..76355de 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (get_rel_relkind(reloid) == RELKIND_VIEW)
+ /* Lock table command ignores global temporary table. */
+ if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+ else if (get_rel_relkind(reloid) == RELKIND_VIEW)
LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
else if (recurse)
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index fa2eea8..3db348d 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"
@@ -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);
/*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqdatatuple;
HeapTuple tuple;
- HeapTuple pgstuple;
- Form_pg_sequence pgsform;
int64 startv;
/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
- pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
- if (!HeapTupleIsValid(pgstuple))
- elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
- pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
- startv = pgsform->seqstart;
- ReleaseSysCache(pgstuple);
+ startv = get_seqence_start_value(seq_relid);
/*
* Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
init_sequence(relid, &elm, &seqrel);
+ if (RELATION_IS_GLOBAL_TEMP(seqrel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+ RelationGetRelationName(seqrel))));
+ }
+
rel = table_open(SequenceRelationId, RowExclusiveLock);
seqtuple = SearchSysCacheCopy1(SEQRELID,
ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ /* Initializes the storage for sequence which the global temporary table belongs. */
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(RelationGetRelid(seqrel)))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+ 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);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 1fa9f19..c2740cc 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -45,6 +45,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"
@@ -559,6 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -604,6 +606,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
@@ -615,7 +618,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !RelpersistenceTsTemp(stmt->relation->relpersistence))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -645,7 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if (RelpersistenceTsTemp(stmt->relation->relpersistence)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -746,6 +749,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* For global temporary table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (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);
+ }
+
+ /* Check oncommit clause and save to reloptions */
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temporary 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 temporary table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1367,7 +1420,7 @@ RemoveRelations(DropStmt *drop)
* relation persistence cannot be known without its OID.
*/
if (drop->concurrent &&
- get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(relOid)))
{
Assert(list_length(drop->objects) == 1 &&
drop->removeType == OBJECT_INDEX);
@@ -1573,7 +1626,16 @@ ExecuteTruncate(TruncateStmt *stmt)
Relation rel;
bool recurse = rv->inh;
Oid myrelid;
- LOCKMODE lockmode = AccessExclusiveLock;
+ LOCKMODE lockmode;
+
+ /*
+ * Truncate global temp table only cleans up the data in current backend,
+ * only low-level locks are required.
+ */
+ if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+ else
+ lockmode = AccessExclusiveLock;
myrelid = RangeVarGetRelidExtended(rv, lockmode,
0, RangeVarCallbackForTruncate,
@@ -1838,6 +1900,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
continue;
/*
+ * Skip the global temporary table that is not initialized for storage
+ * in current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ continue;
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -3681,6 +3751,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temporary table only current backend use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5004,6 +5084,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ {
+ if (tab->chgPersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change global temporary table persistence setting")));
+
+ /*
+ * The storage for the global temporary table needs to be initialized
+ * before rewrite table.
+ */
+ if(!gtt_storage_attached(tab->relid))
+ {
+ ResultRelInfo *resultRelInfo;
+ MemoryContext oldcontext;
+ MemoryContext ctx_alter_gtt;
+
+ ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+ "gtt alter table", ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, OldHeap,
+ 1, NULL, 0);
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
+ init_gtt_storage(CMD_UTILITY, resultRelInfo);
+ ExecCloseIndices(resultRelInfo);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(ctx_alter_gtt);
+ }
+ }
+
/*
* Select destination tablespace (same as original unless user
* requested a change)
@@ -8468,6 +8584,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;
}
/*
@@ -12971,6 +13093,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -13173,6 +13298,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 temporary table");
+
/*
* No work if no change in tablespace.
*/
@@ -13547,7 +13675,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,
@@ -14955,6 +15083,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17631,3 +17760,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
CacheInvalidateRelcache(rel);
}
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ bool res = false;
+ char *sval = defGetString(def);
+
+ /* It has to be a Boolean value */
+ if (!parse_bool(sval, &res))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+ if (res)
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 98270a1..66533d6 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"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+ /* For global temporary table */
+ if (is_gtt)
+ {
+ /* Store relation statistics and transaction information to the localhash */
+ up_gtt_relstats(RelationGetRelid(relation),
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+
+ /* Update relation statistics to local relcache */
+ relation->rd_rel->relpages = (int32) num_pages;
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (!is_gtt &&
+ pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (!is_gtt &&
+ pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (!is_gtt &&
+ pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1301,7 +1324,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(),
@@ -1312,7 +1336,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)))
@@ -1421,6 +1446,13 @@ vac_update_datfrozenxid(void)
}
/*
+ * The relfrozenxid for a global temporary talble is stored in localhash,
+ * not pg_class, See list_all_session_gtt_frozenxids()
+ */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
+ /*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
* to not be set (i.e. set to their respective Invalid*Id)
@@ -1477,6 +1509,43 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /* If enable global temporary table */
+ if (max_active_gtt > 0)
+ {
+ TransactionId safe_age;
+ /* */
+ TransactionId oldest_gtt_frozenxid =
+ list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+ {
+ safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+ if (safe_age < FirstNormalTransactionId)
+ safe_age += FirstNormalTransactionId;
+
+ /*
+ * We tolerate that the minimum age of gtt is less than
+ * the minimum age of conventional tables, otherwise it will
+ * throw warning message.
+ */
+ if (TransactionIdIsNormal(safe_age) &&
+ TransactionIdPrecedes(safe_age, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+ oldest_gtt_frozenxid),
+ errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+ errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+ }
+
+ /*
+ * We need to ensure that the clog required by gtt is not cleand.
+ */
+ if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1829,6 +1898,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
}
/*
+ * Skip those global temporary table that are not initialized in
+ * current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(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
* contents are probably not up-to-date on disk. (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index 6e65103..2f46140 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("views cannot be unlogged because they do not have storage")));
+ /* Global temporary table are not sensible. */
+ if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("views cannot be global temp because they do not have storage")));
+
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly. We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 7179f58..c53db5b 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ /* This is one kind of temp table */
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 97bfc8b..ccf6e09 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"
@@ -605,6 +606,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ /* Init storage for partitioned global temporary table in current backend */
+ 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 ab3d655..bb7b86b 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"
@@ -2286,6 +2287,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ /* Init storage for global temporary table in current backend */
+ 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 627d08b..be2603c 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 1a94b58..df46f36 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6430,7 +6430,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
- if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ if (RELATION_IS_TEMP(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index daf1759..36475df 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/heap.h"
#include "catalog/index.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -229,6 +230,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temporary table in current backend */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 084e00f..606ce15 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2546,6 +2546,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 8f341ac..48c399c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3311,17 +3311,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; }
@@ -11394,19 +11388,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 a56bd86..702bb67 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);
/*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+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 temporary 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 89ee990..6e61a41 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->options = seqoptions;
/*
+ * If a sequence is bound to a global temporary table, then the sequence
+ * must been "global temporary"
+ */
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+ /*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
* clause, the "redundant options" error will point to their occurrence,
@@ -3222,6 +3229,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isforeign = false;
}
cxt.relation = stmt->relation;
+ /* Sets the table persistence to the context */
+ cxt.relation->relpersistence = RelationGetRelPersistence(rel);
cxt.rel = rel;
cxt.inhRelations = NIL;
cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index ed127a1..368b164 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /*
+ * Aotuvacuum cannot vacuum the private data stored in each backend
+ * that belongs to global temporary table, so skip them.
+ */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(classForm->relpersistence))
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index c5e8707..ae3cdfb 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"
@@ -2848,6 +2849,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * Returns 0 if this global temporary table is not initialized in current
+ * backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(RelationGetRelid(relation)))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 96c2aaa..432211d 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
#include "access/subtrans.h"
#include "access/syncscan.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -149,6 +150,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
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* For global temporary table shared hashtable */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index ee912b9..299ed04 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -5037,3 +5038,78 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ uint8 flags = 0;
+ int i = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ /* Disable in standby node */
+ 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];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+
+ if (statusFlags & flags)
+ continue;
+
+ /* Fetch all backend that is belonging to MyDatabaseId */
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->backend_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+ result = proc->backend_gtt_frozenxid;
+
+ /* save backend pid and backend level oldest relfrozenxid */
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->backend_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 26bcce9..9e6c01a 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
/* LWTRANCHE_PARALLEL_APPEND: */
"ParallelAppend",
/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
- "PerXactPredicateList"
+ "PerXactPredicateList",
+ /* LWTRANCHE_GTT_CTL */
+ "GlobalTempTableControl"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 7dc3911..def8956 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -391,6 +391,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
@@ -572,6 +573,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 3319e97..db1eb07 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /*
+ * For global temporary table ,each backend has its own storage,
+ * also only sees its own storage. Use Backendid to identify them.
+ */
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 80bd60f..98b6196 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6450,6 +6478,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;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,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;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,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))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7435,6 +7493,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.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index ad92636..4faf93f 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -3086,6 +3087,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 3bd5e18..ddcbb78 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,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"
@@ -1135,6 +1136,28 @@ 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;
+
+ /* For global temporary table, get relstat data from localhash */
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+
+ /* And put them to local relcache */
+ 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);
@@ -1189,6 +1212,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -1313,7 +1338,22 @@ RelationInitPhysicalAddr(Relation relation)
heap_freetuple(phys_tuple);
}
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ /*
+ * For global temporary table, get the latest relfilenode
+ * from localhash and put it in relcache.
+ */
+ if (OidIsValid(newrelnode) &&
+ newrelnode != relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = newrelnode;
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ }
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
}
else
{
@@ -2254,6 +2294,9 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
}
/* Okay, now it's valid again */
@@ -3480,6 +3523,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;
@@ -3587,28 +3634,38 @@ void
RelationSetNewRelfilenode(Relation relation, char persistence)
{
Oid newrelfilenode;
- Relation pg_class;
- HeapTuple tuple;
+ Relation pg_class = NULL;
+ HeapTuple tuple = NULL;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ /*
+ * For global temporary table, storage information for the table is
+ * maintained locally, not in catalog.
+ */
+ bool update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
- /*
- * Get a writable copy of the pg_class tuple for the given relation.
- */
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ memset(&classform, 0, sizeof(classform));
- tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u",
- RelationGetRelid(relation));
- classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (update_catalog)
+ {
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ }
/*
* Schedule unlinking of the old storage at transaction commit.
@@ -3634,7 +3691,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;
@@ -3654,6 +3711,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
break;
}
+ /* For global temporary table */
+ if (!update_catalog)
+ {
+ Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ Assert(RELATION_IS_GLOBAL_TEMP(relation));
+ Assert(!RelationIsMapped(relation));
+
+ /* Make cache invalid and set new relnode to local cache. */
+ CacheInvalidateRelcache(relation);
+ relation->rd_node.relNode = relnode;
+ }
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
@@ -3663,7 +3732,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
- if (RelationIsMapped(relation))
+ else if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3709,9 +3778,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
}
- heap_freetuple(tuple);
+ if (update_catalog)
+ {
+ heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ table_close(pg_class, RowExclusiveLock);
+ }
/*
* Make the pg_class row change or relation map change visible. This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dabcbb0..5e3b5fc 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
@@ -145,6 +146,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2036,6 +2049,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."),
@@ -2554,6 +2576,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+ NULL
+ },
+ &vacuum_gtt_defer_check_age,
+ 10000, 0, 1000000,
+ NULL, NULL, NULL
+ },
+
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8b1e5cc..ad05d58 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2433,6 +2433,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data in global temporary table/sequence */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -15717,6 +15721,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
char *ftoptions = NULL;
char *srvname = NULL;
char *foreign = "";
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15770,9 +15775,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);
@@ -16158,13 +16169,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/*
+ * Transaction information for the global temporary table is not stored
+ * in the pg_class.
+ */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(tbinfo->frozenxid == 0);
+ Assert(tbinfo->minmxid == 0);
+ }
+ /*
* In binary_upgrade mode, arrange to restore the old relfrozenxid and
* relminmxid of all vacuumable relations. (While vacuum.c processes
* TOAST tables semi-independently, here we see them only as children
* of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
* child toast table is handled below.)
*/
- if (dopt->binary_upgrade &&
+ else if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_MATVIEW))
{
@@ -17096,6 +17116,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer delqry = createPQExpBuffer();
char *qseqname;
+ bool global_temp_seq = false;
qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
@@ -17105,9 +17126,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
"SELECT format_type(seqtypid, NULL), "
"seqstart, seqincrement, "
"seqmax, seqmin, "
- "seqcache, seqcycle "
- "FROM pg_catalog.pg_sequence "
- "WHERE seqrelid = '%u'::oid",
+ "seqcache, seqcycle, "
+ "c.relpersistence "
+ "FROM pg_catalog.pg_sequence s, "
+ "pg_catalog.pg_class c "
+ "WHERE seqrelid = '%u'::oid "
+ "and s.seqrelid = c.oid",
tbinfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
@@ -17152,6 +17176,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
cache = PQgetvalue(res, 0, 5);
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ if (fout->remoteVersion >= 140000)
+ global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
/* Calculate default limits for a sequence of this type */
is_ascending = (incby[0] != '-');
if (strcmp(seqtype, "smallint") == 0)
@@ -17229,9 +17256,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
else
{
- appendPQExpBuffer(query,
- "CREATE SEQUENCE %s\n",
- fmtQualifiedDumpable(tbinfo));
+ appendPQExpBuffer(query, "CREATE ");
+
+ if (global_temp_seq)
+ appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+ appendPQExpBuffer(query, "SEQUENCE %s\n",
+ fmtQualifiedDumpable(tbinfo));
if (strcmp(seqtype, "bigint") != 0)
appendPQExpBuffer(query, " AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 6685d51..e37d9bf 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,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();
@@ -166,7 +166,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..5725cbe 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);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
+ CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+ if (skip_gtt)
+ {
+ /* exclude global temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+ }
+
/* exclude possible orphaned temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
" n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e2253ec..32eccd4 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,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);
}
/*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ee70243..a69089c 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,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 14150d0..b86a2a6 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3756,7 +3756,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 3a43c09..ef0e6eb 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1044,6 +1044,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},
@@ -2446,6 +2448,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" */
@@ -2654,6 +2659,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/heap.h b/src/include/catalog/heap.h
index f94deff..056fe64 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid);
+ MultiXactId *relminmxid,
+ bool skip_create_storage);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index bb5e72c..bab9599 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
#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 22970f4..5817a18 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5590,6 +5590,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4572',
+ 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 => '4573',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4574',
+ 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 => '4575',
+ descr => 'List those backends that have used global temporary table',
+ proname => 'pg_list_gtt_relfrozenxids', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => '',
+ proallargtypes => '{int4,xid}',
+ proargmodes => '{o,o}',
+ proargnames => '{pid,relfrozenxid}',
+ prosrc => 'pg_list_gtt_relfrozenxids' },
+
{ oid => '3788',
descr => 'statistics: timestamp of the current statistics snapshot',
proname => 'pg_stat_get_snapshot_timestamp', provolatile => 's',
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 30c38e0..7ff2408 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
/* GUC variables */
extern int wal_skip_threshold;
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..d48162c
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+ 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 force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index e2638ab..89a5ce4 100644
--- a/src/include/commands/sequence.h
+++ b/src/include/commands/sequence.h
@@ -65,5 +65,6 @@ extern void seq_redo(XLogReaderState *rptr);
extern void seq_desc(StringInfo buf, XLogReaderState *rptr);
extern const char *seq_identify(uint8 info);
extern void seq_mask(char *pagedata, BlockNumber blkno);
+extern void gtt_init_seq(Relation rel);
#endif /* SEQUENCE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index 93f9446..14cafae 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -120,4 +120,7 @@ extern Oid attnumTypeId(Relation rd, int attid);
extern Oid attnumCollationId(Relation rd, int attid);
extern bool isQueryUsingTempRelation(Query *query);
+/* global temp table check */
+extern bool is_query_using_gtt(Query *query);
+
#endif /* PARSE_RELATION_H */
diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h
index d0a52f8..8fa493f 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,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 af9b417..6e16f99 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e77f76a..7b8786d 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -156,6 +156,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId backend_gtt_frozenxid; /* backend 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 ea8a876..ed87772 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -92,4 +92,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_backend_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 6a20a3b..ed7d517 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,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 c5ffea4..6dda0b6 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
@@ -306,6 +306,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
@@ -571,11 +572,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.
*
@@ -583,6 +586,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ * Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+ (relpersistence == RELPERSISTENCE_TEMP || \
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep
+----------
+
+(1 row)
+
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR: views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+ 1 | 1
+ 2 | 3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2
+----+----
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 33 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
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..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+ gtt_stats | gtt_pkey | 1 | 0 | 0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+ gtt_stats | gtt_pkey | 30 | 10000 | 0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6293ab5..e362510 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relfilenode,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef7..c8148f0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -123,3 +123,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
Hi
út 22. 12. 2020 v 4:20 odesílatel wenjing <wjzeng2012@gmail.com> napsal:
HI all
I rebased patch, fix OID conflicts, fix some comments.
Code updates to v41.
Please, can you do rebase?
Regards
Pavel
Show quoted text
Wenjing
Pavel Stehule <pavel.stehule@gmail.com> 于2021年1月26日周二 上午2:03写道:
Hi
út 22. 12. 2020 v 4:20 odesílatel wenjing <wjzeng2012@gmail.com> napsal:
HI all
I rebased patch, fix OID conflicts, fix some comments.
Code updates to v41.Please, can you do rebase?
Regards
Pavel
Wenjing
Sure.
This patch(V42) is base on aa6e46daf5304e8d9e66fefc1a5bd77622ec6402
Wenjing
Attachments:
global_temporary_table_v42-pg14.patchapplication/octet-stream; name=global_temporary_table_v42-pg14.patchDownload
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index c687d3e..63b827c 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -168,6 +168,19 @@ static relopt_bool boolRelOpts[] =
},
true
},
+ /*
+ * In order to avoid consistency problems, the global temporary table
+ * uses ShareUpdateExclusiveLock.
+ */
+ {
+ {
+ "on_commit_delete_rows",
+ "global temporary table on commit options",
+ RELOPT_KIND_HEAP | RELOPT_KIND_PARTITIONED,
+ ShareUpdateExclusiveLock
+ },
+ true
+ },
/* list terminator */
{{NULL}}
};
@@ -1817,6 +1830,8 @@ bytea *
default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
{
static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)},
{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
{"autovacuum_enabled", RELOPT_TYPE_BOOL,
offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
@@ -1962,12 +1977,16 @@ bytea *
partitioned_table_reloptions(Datum reloptions, bool validate)
{
/*
- * There are no options for partitioned tables yet, but this is able to do
- * some validation.
+ * Add option for global temp partitioned tables.
*/
+ static const relopt_parse_elt tab[] = {
+ {"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+ offsetof(StdRdOptions, on_commit_delete_rows)}
+ };
+
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 cf53dad..5867953 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1026,7 +1026,7 @@ gistproperty(Oid index_oid, int attno,
XLogRecPtr
gistGetFakeLSN(Relation rel)
{
- if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+ if (RELATION_IS_TEMP(rel))
{
/*
* Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 0752fb3..5c85d77 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -151,7 +151,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
* metapage, nor the first bitmap page.
*/
sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
- if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+ if (!RELATION_IS_TEMP(index))
sort_threshold = Min(sort_threshold, NBuffers);
else
sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 4a70e20..2464e89 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -589,7 +589,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
@@ -642,7 +642,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 f3d2265..79bad47 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"
@@ -445,9 +446,13 @@ 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.
+ * Transaction information for the global temp table will be stored
+ * in the local hash table, not the catalog.
+ */
+ 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 41dc3f8..0cce3e5 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"
@@ -614,6 +615,14 @@ _bt_getrootheight(Relation rel)
{
Buffer metabuf;
+ /*
+ * If a global temporary table storage file is not initialized in the
+ * current backend, its index does not have a root page, just returns 0.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ return 0;
+
metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ);
metad = _bt_getmeta(rel, metabuf);
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index f03bd47..5c47721 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6958,6 +6958,16 @@ StartupXLOG(void)
int rmid;
/*
+ * When some backend processes crash, those storage files that belong to
+ * global temporary tables will not be cleaned up and remain in the data
+ * directory.
+ * These data files no longer belong to any session and will be cleaned up
+ * during the recovery.
+ */
+ if (max_active_gtt > 0)
+ RemovePgTempFiles();
+
+ /*
* Update pg_control to show that we are recovering and to show the
* selected checkpoint as the place we are starting from. We also mark
* pg_control with any minimum recovery stop point obtained from a
diff --git a/src/backend/bootstrap/bootparse.y b/src/backend/bootstrap/bootparse.y
index 5fcd004..58b994c 100644
--- a/src/backend/bootstrap/bootparse.y
+++ b/src/backend/bootstrap/bootparse.y
@@ -212,7 +212,8 @@ Boot_CreateStmt:
mapped_relation,
true,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
elog(DEBUG4, "bootstrap relation created");
}
else
diff --git a/src/backend/catalog/GTT_README b/src/backend/catalog/GTT_README
new file mode 100644
index 0000000..3a0ec50
--- /dev/null
+++ b/src/backend/catalog/GTT_README
@@ -0,0 +1,170 @@
+Global Temporary Table(GTT)
+==============
+
+Feature description
+--------------------------------
+
+Previously, temporary tables are defined once and automatically
+created (starting with empty contents) in every session before using them.
+
+The temporary table implementation in PostgreSQL, known as Local temp tables(LTT),
+did not fully comply with the SQL standard. This version added the support of
+Global Temporary Table .
+
+The metadata of Global Temporary Table is persistent and shared among sessions.
+The data stored in the Global temporary table is independent of sessions. This
+means, when a session creates a Global Temporary Table and writes some data.
+Other sessions cannot see those data, but they have an empty Global Temporary
+Table with same schema.
+
+Like local temporary table, Global Temporary Table supports ON COMMIT PRESERVE ROWS
+or ON COMMIT DELETE ROWS clause, so that data in the temporary table can be
+cleaned up or reserved automatically when a session exits or a transaction COMMITs.
+
+Unlike Local Temporary Table, Global Temporary Table does not support
+ON COMMIT DROP clauses.
+
+In following paragraphs, we use GTT for Global Temporary Table and LTT for
+local temporary table.
+
+Main design ideas
+-----------------------------------------
+
+STORAGE & BUFFER
+
+In general, GTT and LTT use the same storage and buffer design and
+implementation. The storage files for both types of temporary tables are named
+as t_backendid_relfilenode, and the local buffer is used to cache the data.
+
+The schema of GTTs is shared among sessions while their data are not. We build
+a new mechanisms to manage those non-shared data and their statistics.
+Here is the summary of changes:
+
+1) CATALOG
+GTTs store session-specific data. The storage information of GTTs'data, their
+transaction information, and their statistics are not stored in the catalog.
+
+2) STORAGE INFO & STATISTICS & TRANSACTION
+In order to maintain durability and availability of GTTs'session-specific data,
+their storage information, statistics, and transaction information is managed
+in a local hash table tt_storage_local_hash.
+
+3) DDL
+A shared hash table active_gtt_shared_hash is added to track the state of the
+GTT in a different session. This information is recorded in the hash table
+during the DDL execution of the GTT.
+
+4) LOCK
+The data stored in a GTT can only be modified or accessed by owning session.
+The statements that only modify data in a GTT do not need a high level of table
+locking.
+Changes to the GTT's metadata affect all sessions.
+The operations making those changes include truncate GTT, Vacuum/Cluster GTT,
+and Lock GTT.
+
+Detailed design
+-----------------------------------------
+
+1. CATALOG
+1.1 relpersistence
+define RELPERSISTENCEGLOBALTEMP 'g'
+Mark Global Temporary Table in pg_class relpersistence to 'g'. The relpersistence
+of indexes created on the GTT, sequences on GTT and toast tables on GTT are
+also set to 'g'
+
+1.2 on commit clause
+LTT's status associated with on commit DELETE ROWS and on commit PRESERVE ROWS
+is not stored in catalog. Instead, GTTs need a bool value on_commit_delete_rows
+in reloptions which is shared among sessions.
+
+1.3 gram.y
+GTT is already supported in syntax tree. We remove the warning message
+"GLOBAL is deprecated in temporary table creation" and mark
+relpersistence = RELPERSISTENCEGLOBALTEMP.
+
+2. STORAGE INFO & STATISTICS DATA & TRANSACTION INFO
+2.1. gtt_storage_local_hash
+Each backend creates a local hashtable gtt_storage_local_hash to track a GTT's
+storage file information, statistics, and transaction information.
+
+2.2 GTT storage file info track
+1) When one session inserts data into a GTT for the first time, record the
+storage info to gtt_storage_local_hash.
+2) Use beforeshmemexit to ensure that all files ofa session GTT are deleted when
+the session exits.
+3) GTT storage file cleanup during abnormal situations
+When a backend exits abnormally (such as oom kill), the startup process starts
+recovery before accepting client connection. The same startup process checks
+nd removes all GTT files before redo WAL.
+
+2.3 statistics info
+1) relpages reltuples relallvisible
+2) The statistics of each column from pg_statistic
+All the above information is stored in gtt_storage_local_hash.
+When doing vacuum or analyze, GTT's statistic is updated, which is used by
+the SQL planner.
+The statistics summarizes only data in the current session.
+
+2.3 transaction info track
+frozenxid minmulti from pg_class is stored to gtt_storage_local_hash.
+
+4 DDL
+4.1. active_gtt_shared_hash
+This is the hash table created in shared memory to trace the GTT files initialized
+in each session. Each hash entry contains a bitmap that records the backendid of
+the initialized GTT file. With this hash table, we know which backend/session
+is using this GTT. Such information is used during GTT's DDL operations.
+
+4.1 DROP GTT
+One GTT is allowed to be deleted when there is only one session using the table
+and the session is the current session.
+After holding the AccessExclusiveLock lock on GTT, active_gtt_shared_hash
+is checked to ensure that.
+
+4.2 ALTER GTT/DROP INDEX ON GTT
+Same as drop GTT.
+
+4.3 CREATE INDEX ON GTT
+1) create index on GTT statements build index based on local data in a session.
+2) After the index is created, record the index metadata to the catalog.
+3) Other sessions can enable or disable the local GTT index.
+
+5 LOCK
+
+5.1 TRUNCATE GTT
+The truncate GTT command uses RowExclusiveLock, not AccessExclusiveLock, because
+this command only cleans up local data and local buffers in current session.
+
+5.2 CLUSTER GTT/VACUUM FULL GTT
+Same as truncate GTT.
+
+5.3 Lock GTT
+A lock GTT statement does not hold any table locks.
+
+6 MVCC commit log(clog) cleanup
+
+The GTT storage file contains transaction information. Queries for GTT data rely
+on transaction information such as clog. The transaction information required by
+each session may be completely different. We need to ensure that the transaction
+information of the GTT data is not cleaned up during its lifetime and that
+transaction resources are recycled at the instance level.
+
+6.1 The session level GTT oldest frozenxid
+1) To manage all GTT transaction information, add session level oldest frozenxid
+in each session. When one GTT is created or removed, record the session level
+oldest frozenxid and store it in MyProc.
+2) When vacuum advances the database's frozenxid, session level oldest frozenxid
+should be considered. This is acquired by searching all of MyProc. This way,
+we can avoid the clog required by GTTs to be cleaned.
+
+6.2 vacuum GTT
+Users can perform vacuum over a GTT to clean up local data in the GTT.
+
+6.3 autovacuum GTT
+Autovacuum skips all GTTs, because the data in GTTs is only visible in current
+session.
+
+7 OTHERS
+Parallel query
+Planner does not produce parallel query plans for SQL related to GTT. Because
+GTT private data cannot be accessed across processes.
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 995ddf1..2e6e588 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -43,6 +43,7 @@ OBJS = \
pg_subscription.o \
pg_type.o \
storage.o \
+ storage_gtt.o \
toasting.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index e2ed80a..81d19c5 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -392,6 +392,7 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
switch (relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
break;
case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 9abc4a1..ea05776 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,
@@ -304,7 +306,8 @@ heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid)
+ MultiXactId *relminmxid,
+ bool skip_create_storage)
{
bool create_storage;
Relation rel;
@@ -369,7 +372,9 @@ heap_create(const char *relname,
* storage is already created, so don't do it here. Also don't create it
* for relkinds without physical storage.
*/
- if (!RELKIND_HAS_STORAGE(relkind) || OidIsValid(relfilenode))
+ if (!RELKIND_HAS_STORAGE(relkind) ||
+ OidIsValid(relfilenode) ||
+ skip_create_storage)
create_storage = false;
else
{
@@ -427,7 +432,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:
@@ -997,6 +1002,7 @@ AddNewRelationTuple(Relation pg_class_desc,
Oid reloftype,
Oid relowner,
char relkind,
+ char relpersistence,
TransactionId relfrozenxid,
TransactionId relminmxid,
Datum relacl,
@@ -1035,8 +1041,21 @@ AddNewRelationTuple(Relation pg_class_desc,
break;
}
- new_rel_reltup->relfrozenxid = relfrozenxid;
- new_rel_reltup->relminmxid = relminmxid;
+ /*
+ * The transaction information of the global temporary table is stored
+ * in the local hash table, not 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;
@@ -1301,7 +1320,8 @@ heap_create_with_catalog(const char *relname,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ false);
Assert(relid == RelationGetRelid(new_rel_desc));
@@ -1408,6 +1428,7 @@ heap_create_with_catalog(const char *relname,
reloftypeid,
ownerid,
relkind,
+ relpersistence,
relfrozenxid,
relminmxid,
PointerGetDatum(relacl),
@@ -1987,6 +2008,19 @@ heap_drop_with_catalog(Oid relid)
update_default_partition_oid(parentOid, InvalidOid);
/*
+ * Global temporary table is allowed to be dropped only when the
+ * current session is using it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
+ /*
* Schedule unlinking of the relation's physical files at commit.
*/
if (RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
@@ -3249,7 +3283,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;
@@ -3261,7 +3295,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
@@ -3307,8 +3341,16 @@ heap_truncate(List *relids)
{
Oid rid = lfirst_oid(cell);
Relation rel;
+ LOCKMODE lockmode = AccessExclusiveLock;
- rel = table_open(rid, AccessExclusiveLock);
+ /*
+ * Truncate global temporary table only clears local data,
+ * so only low-level locks need to be held.
+ */
+ if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+
+ rel = table_open(rid, lockmode);
relations = lappend(relations, rel);
}
@@ -3341,6 +3383,7 @@ void
heap_truncate_one_rel(Relation rel)
{
Oid toastrelid;
+ LOCKMODE lockmode = AccessExclusiveLock;
/*
* Truncate the relation. Partitioned tables have no storage, so there is
@@ -3349,23 +3392,47 @@ heap_truncate_one_rel(Relation rel)
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
return;
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ /*
+ * If this GTT is not initialized in current backend, there is
+ * no needs to anything.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(rel)))
+ return;
+
+ /*
+ * Truncate GTT only clears local data, so only low-level locks
+ * need to be held.
+ */
+ 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);
}
+
+ /*
+ * After the data is cleaned up on the GTT, the transaction information
+ * for the data(stored in local hash table) is also need reset.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ up_gtt_relstats(RelationGetRelid(rel), 0, 0, 0, RecentXmin, InvalidMultiXactId);
}
/*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index b8cd35e..304c6c4 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -54,6 +54,7 @@
#include "catalog/pg_type.h"
#include "catalog/storage.h"
#include "commands/defrem.h"
+#include "catalog/storage_gtt.h"
#include "commands/event_trigger.h"
#include "commands/progress.h"
#include "commands/tablecmds.h"
@@ -720,6 +721,29 @@ index_create(Relation heapRelation,
char relkind;
TransactionId relfrozenxid;
MultiXactId relminmxid;
+ bool skip_create_storage = false;
+
+ /* For global temporary table only */
+ if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+ {
+ /* No support create index on global temporary table with concurrent mode yet */
+ if (concurrent)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot reindex global temporary tables concurrently")));
+
+ /*
+ * For the case that some backend is applied relcache message to create
+ * an index on a global temporary table, if this table in the current
+ * backend are not initialized, the creation of index storage on the
+ * table are also skipped.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(heapRelation)))
+ {
+ skip_create_storage = true;
+ flags |= INDEX_CREATE_SKIP_BUILD;
+ }
+ }
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -927,7 +951,8 @@ index_create(Relation heapRelation,
mapped_relation,
allow_system_table_mods,
&relfrozenxid,
- &relminmxid);
+ &relminmxid,
+ skip_create_storage);
Assert(relfrozenxid == InvalidTransactionId);
Assert(relminmxid == InvalidMultiXactId);
@@ -2200,7 +2225,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
* lock (see comments in RemoveRelations), and a non-concurrent DROP is
* more efficient.
*/
- Assert(get_rel_persistence(indexId) != RELPERSISTENCE_TEMP ||
+ Assert(!RelpersistenceTsTemp(get_rel_persistence(indexId)) ||
(!concurrent && !concurrent_lock_mode));
/*
@@ -2233,6 +2258,20 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
CheckTableNotInUse(userIndexRelation, "DROP INDEX");
/*
+ * Allow to drop index on global temporary table when only current
+ * backend use it.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(userHeapRelation) &&
+ is_other_backend_use_gtt(RelationGetRelid(userHeapRelation)))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot drop index %s or global temporary table %s",
+ RelationGetRelationName(userIndexRelation), RelationGetRelationName(userHeapRelation)),
+ errhint("Because the index is created on the global temporary table and other backend attached it.")));
+ }
+
+ /*
* Drop Index Concurrently is more or less the reverse process of Create
* Index Concurrently.
*
@@ -2840,6 +2879,7 @@ index_update_stats(Relation rel,
HeapTuple tuple;
Form_pg_class rd_rel;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(rel);
/*
* We always update the pg_class row using a non-transactional,
@@ -2934,20 +2974,37 @@ index_update_stats(Relation rel,
else /* don't bother for indexes */
relallvisible = 0;
- if (rd_rel->relpages != (int32) relpages)
- {
- rd_rel->relpages = (int32) relpages;
- dirty = true;
- }
- if (rd_rel->reltuples != (float4) reltuples)
+ /* For global temporary table */
+ if (is_gtt)
{
- rd_rel->reltuples = (float4) reltuples;
- dirty = true;
+ /* Update GTT'statistics into local relcache */
+ rel->rd_rel->relpages = (int32) relpages;
+ rel->rd_rel->reltuples = (float4) reltuples;
+ rel->rd_rel->relallvisible = (int32) relallvisible;
+
+ /* Update GTT'statistics into local hashtable */
+ up_gtt_relstats(RelationGetRelid(rel), relpages, reltuples, relallvisible,
+ InvalidTransactionId, InvalidMultiXactId);
}
- if (rd_rel->relallvisible != (int32) relallvisible)
+ else
{
- rd_rel->relallvisible = (int32) relallvisible;
- dirty = true;
+ if (rd_rel->relpages != (int32) relpages)
+ {
+ rd_rel->relpages = (int32) relpages;
+ dirty = true;
+ }
+
+ if (rd_rel->reltuples != (float4) reltuples)
+ {
+ rd_rel->reltuples = (float4) reltuples;
+ dirty = true;
+ }
+
+ if (rd_rel->relallvisible != (int32) relallvisible)
+ {
+ rd_rel->relallvisible = (int32) relallvisible;
+ dirty = true;
+ }
}
}
@@ -3062,6 +3119,26 @@ index_build(Relation heapRelation,
pgstat_progress_update_multi_param(6, index, val);
}
+ /* For build index on global temporary table */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+ {
+ /*
+ * If the storage for the index in this session is not initialized,
+ * it needs to be created.
+ */
+ if (!gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ /* Before create init storage, fix the local Relcache first */
+ force_enable_gtt_index(indexRelation);
+
+ Assert(gtt_storage_attached(RelationGetRelid(heapRelation)));
+
+ /* Init storage for index */
+ RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+
+ }
+ }
+
/*
* Call the access method's build procedure
*/
@@ -3616,6 +3693,20 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
if (!OidIsValid(heapId))
return;
+ /*
+ * For reindex on global temporary table, If the storage for the index
+ * in current backend is not initialized, nothing is done.
+ */
+ if (persistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ !gtt_storage_attached(indexId))
+ {
+ /* Suppress use of the target index while rebuilding it */
+ SetReindexProcessing(heapId, indexId);
+ /* Re-allow use of target index */
+ ResetReindexProcessing();
+ return;
+ }
+
if ((params->options & REINDEXOPT_MISSING_OK) != 0)
heapRelation = try_table_open(heapId, ShareLock);
else
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index 005e029..5e59b47 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -655,6 +655,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
errmsg("cannot create temporary relation in non-temporary schema")));
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /* Do not allow create global temporary table in temporary schemas */
+ if (isAnyTempNamespace(nspid))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+ errmsg("cannot create global temp table 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 cba7a9a..9cf6802 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -27,6 +27,7 @@
#include "access/xlogutils.h"
#include "catalog/storage.h"
#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "storage/freespace.h"
#include "storage/smgr.h"
@@ -61,6 +62,7 @@ typedef struct PendingRelDelete
{
RelFileNode relnode; /* relation that may need to be deleted */
BackendId backend; /* InvalidBackendId if not a temp rel */
+ Oid temprelOid; /* InvalidOid if not a global temporary rel */
bool atCommit; /* T=delete at commit; F=delete at abort */
int nestLevel; /* xact nesting level of request */
struct PendingRelDelete *next; /* linked-list link */
@@ -115,7 +117,7 @@ AddPendingSync(const RelFileNode *rnode)
* transaction aborts later on, the storage will be destroyed.
*/
SMgrRelation
-RelationCreateStorage(RelFileNode rnode, char relpersistence)
+RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel)
{
PendingRelDelete *pending;
SMgrRelation srel;
@@ -126,7 +128,12 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
switch (relpersistence)
{
+ /*
+ * Global temporary table and local temporary table use same
+ * design on storage module.
+ */
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
backend = BackendIdForTempRelations();
needs_wal = false;
break;
@@ -154,6 +161,7 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rnode;
pending->backend = backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = false; /* delete if abort */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
@@ -165,6 +173,21 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
AddPendingSync(&rnode);
}
+ if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(rel && RELATION_IS_GLOBAL_TEMP(rel));
+
+ /*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /* Remember global temporary table storage info to localhash */
+ remember_gtt_storage_info(rnode, rel);
+ }
+
return srel;
}
@@ -201,12 +224,21 @@ RelationDropStorage(Relation rel)
MemoryContextAlloc(TopMemoryContext, sizeof(PendingRelDelete));
pending->relnode = rel->rd_node;
pending->backend = rel->rd_backend;
+ pending->temprelOid = InvalidOid;
pending->atCommit = true; /* delete if commit */
pending->nestLevel = GetCurrentTransactionNestLevel();
pending->next = pendingDeletes;
pendingDeletes = pending;
/*
+ * Remember the reloid of global temporary table, which is used for
+ * transaction commit or rollback.
+ * see smgrDoPendingDeletes.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ pending->temprelOid = RelationGetRelid(rel);
+
+ /*
* NOTE: if the relation was created in this transaction, it will now be
* present in the pending-delete list twice, once with atCommit true and
* once with atCommit false. Hence, it will be physically deleted at end
@@ -602,6 +634,7 @@ smgrDoPendingDeletes(bool isCommit)
int nrels = 0,
maxrels = 0;
SMgrRelation *srels = NULL;
+ Oid *reloids = NULL;
prev = NULL;
for (pending = pendingDeletes; pending != NULL; pending = next)
@@ -631,14 +664,18 @@ smgrDoPendingDeletes(bool isCommit)
{
maxrels = 8;
srels = palloc(sizeof(SMgrRelation) * maxrels);
+ reloids = palloc(sizeof(Oid) * maxrels);
}
else if (maxrels <= nrels)
{
maxrels *= 2;
srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+ reloids = repalloc(reloids, sizeof(Oid) * maxrels);
}
- srels[nrels++] = srel;
+ srels[nrels] = srel;
+ reloids[nrels] = pending->temprelOid;
+ nrels++;
}
/* must explicitly free the list entry */
pfree(pending);
@@ -648,12 +685,21 @@ smgrDoPendingDeletes(bool isCommit)
if (nrels > 0)
{
+ int i;
+
smgrdounlinkall(srels, nrels, false);
- for (int i = 0; i < nrels; i++)
+ for (i = 0; i < nrels; i++)
+ {
smgrclose(srels[i]);
+ /* Delete global temporary table info in localhash */
+ if (gtt_storage_attached(reloids[i]))
+ forget_gtt_storage_info(reloids[i], srels[i]->smgr_rnode.node, isCommit);
+ }
+
pfree(srels);
+ pfree(reloids);
}
}
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..3ff0c3a
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,1648 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ * The body implementation of Global Temparary table.
+ *
+ * IDENTIFICATION
+ * src/backend/catalog/storage_gtt.c
+ *
+ * See src/backend/catalog/GTT_README for Global temparary table's
+ * requirements and design.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/amapi.h"
+#include "access/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_info_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+int vacuum_gtt_defer_check_age = 0;
+
+/*
+ * The Global temporary table's shared hash table data structure
+ */
+typedef struct gtt_ctl_data
+{
+ LWLock lock;
+ int max_entry;
+ int entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct gtt_fnode
+{
+ Oid dbNode;
+ Oid relNode;
+} gtt_fnode;
+
+/* record this global temporary table in which backends are being used */
+typedef struct
+{
+ gtt_fnode rnode;
+ Bitmapset *map;
+ /* bitmap data */
+} gtt_shared_hash_entry;
+
+/*
+ * The Global temporary table's local hash table data structure
+ */
+/* Record the storage information and statistical information of the global temporary table */
+typedef struct
+{
+ Oid relfilenode;
+ Oid spcnode;
+
+ /* pg_class relstat */
+ int32 relpages;
+ float4 reltuples;
+ int32 relallvisible;
+ TransactionId relfrozenxid;
+ TransactionId relminmxid;
+
+ /* pg_statistic column stat */
+ int natts;
+ int *attnum;
+ HeapTuple *att_stat_tups;
+} gtt_relfilenode;
+
+typedef struct
+{
+ Oid relid;
+
+ List *relfilenode_list;
+
+ char relkind;
+ bool on_commit_delete;
+
+ Oid oldrelid; /* remember the source of relid, before the switch relfilenode. */
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(Oid relid);
+static void gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+static void gtt_free_statistics(gtt_relfilenode *rnode);
+static gtt_relfilenode *gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok);
+static gtt_local_hash_entry *gtt_search_by_relid(Oid relid, bool missing_ok);
+static Bitmapset *copy_active_gtt_bitmap(Oid relid);
+
+Datum pg_get_gtt_statistics(PG_FUNCTION_ARGS);
+Datum pg_get_gtt_relstats(PG_FUNCTION_ARGS);
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+
+/*
+ * Calculate shared hash table entry size for GTT.
+ */
+static Size
+action_gtt_shared_hash_entry_size(void)
+{
+ int wordnum;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ wordnum = WORDNUM(MaxBackends + 1);
+ /* hash entry header size */
+ hash_entry_size += MAXALIGN(sizeof(gtt_shared_hash_entry));
+ /*
+ * hash entry data size
+ * this is a bitmap in shared memory, each backend have a bit.
+ */
+ hash_entry_size += MAXALIGN(BITMAPSET_SIZE(wordnum + 1));
+
+ return hash_entry_size;
+}
+
+/*
+ * Calculate shared hash table max size for GTT.
+ */
+Size
+active_gtt_shared_hash_size(void)
+{
+ Size size = 0;
+ Size hash_entry_size = 0;
+
+ if (max_active_gtt <= 0)
+ return 0;
+
+ /* shared hash header size */
+ size = MAXALIGN(sizeof(gtt_ctl_data));
+ /* hash entry size */
+ hash_entry_size = action_gtt_shared_hash_entry_size();
+ /* max size */
+ size += hash_estimate_size(max_active_gtt, hash_entry_size);
+
+ return size;
+}
+
+/*
+ * Initialization shared hash table for GTT.
+ */
+void
+active_gtt_shared_hash_init(void)
+{
+ HASHCTL info;
+ bool found;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ gtt_shared_ctl =
+ ShmemInitStruct("gtt_shared_ctl",
+ sizeof(gtt_ctl_data),
+ &found);
+
+ if (!found)
+ {
+ LWLockRegisterTranche(LWTRANCHE_GTT_CTL, "gtt_shared_ctl");
+ LWLockInitialize(>t_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+ gtt_shared_ctl->max_entry = max_active_gtt;
+ gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+ }
+
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(gtt_fnode);
+ info.entrysize = action_gtt_shared_hash_entry_size();
+ active_gtt_shared_hash =
+ ShmemInitHash("active gtt shared hash",
+ gtt_shared_ctl->max_entry,
+ gtt_shared_ctl->max_entry,
+ &info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+/*
+ * Record GTT relid to shared hash table, which means that current backend is using this GTT.
+ */
+static void
+gtt_storage_checkin(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool found;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *)&(fnode), HASH_ENTER_NULL, &found);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ ereport(ERROR,
+ (errcode(ERRCODE_OUT_OF_MEMORY),
+ errmsg("out of shared memory"),
+ errhint("You might need to increase max_active_global_temporary_table.")));
+ }
+
+ if (!found)
+ {
+ int wordnum;
+
+ /* init bitmap */
+ entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+ wordnum = WORDNUM(MaxBackends + 1);
+ memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+ entry->map->nwords = wordnum + 1;
+ }
+
+ /* record itself in bitmap */
+ bms_add_member(entry->map, MyBackendId);
+ LWLockRelease(>t_shared_ctl->lock);
+}
+
+/*
+ * Remove the GTT relid record from the shared hash table which means that current backend is
+ * not use this GTT.
+ */
+static void
+gtt_storage_checkout(Oid relid, bool skiplock, bool isCommit)
+{
+ gtt_shared_hash_entry *entry;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ if (!skiplock)
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ if (isCommit)
+ elog(WARNING, "relid %u not exist in gtt shared hash when forget", relid);
+
+ return;
+ }
+
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* remove itself from bitmap */
+ bms_del_member(entry->map, MyBackendId);
+
+ if (bms_is_empty(entry->map))
+ {
+ if (!hash_search(active_gtt_shared_hash, &fnode, HASH_REMOVE, NULL))
+ elog(PANIC, "gtt shared hash table corrupted");
+ }
+
+ if (!skiplock)
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return;
+}
+
+/*
+ * Gets usage information for a GTT from shared hash table.
+ * The information is in the form of bitmap.
+ * Quickly copy the entire bitmap from shared memory and return it.
+ * that to avoid holding locks for a long time.
+ */
+static Bitmapset *
+copy_active_gtt_bitmap(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ Bitmapset *map_copy = NULL;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return NULL;
+ }
+
+ Assert(entry->map);
+
+ /* copy the entire bitmap */
+ if (!bms_is_empty(entry->map))
+ map_copy = bms_copy(entry->map);
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return map_copy;
+}
+
+/*
+ * Check if there are other backends using this GTT besides the current backand.
+ */
+bool
+is_other_backend_use_gtt(Oid relid)
+{
+ gtt_shared_hash_entry *entry;
+ bool in_use = false;
+ int num_use = 0;
+ gtt_fnode fnode;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ fnode.dbNode = MyDatabaseId;
+ fnode.relNode = relid;
+ LWLockAcquire(>t_shared_ctl->lock, LW_SHARED);
+ entry = hash_search(active_gtt_shared_hash,
+ (void *) &(fnode), HASH_FIND, NULL);
+
+ if (entry == NULL)
+ {
+ LWLockRelease(>t_shared_ctl->lock);
+ return false;
+ }
+
+ Assert(entry->map);
+ Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+ /* how many backend are using this GTT */
+ num_use = bms_num_members(entry->map);
+ if (num_use == 0)
+ in_use = false;
+ else if (num_use == 1)
+ {
+ /* check if this is itself */
+ if(bms_is_member(MyBackendId, entry->map))
+ in_use = false;
+ else
+ in_use = true;
+ }
+ else
+ in_use = true;
+
+ LWLockRelease(>t_shared_ctl->lock);
+
+ return in_use;
+}
+
+/*
+ * Record GTT information to local hash.
+ * They include GTT storage info, transaction info and statistical info.
+ */
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ gtt_relfilenode *new_node = NULL;
+ Oid relid = RelationGetRelid(rel);
+ int natts = 0;
+
+ if (max_active_gtt <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Global temporary table feature is disable"),
+ errhint("You might need to increase max_active_global_temporary_table to enable this feature.")));
+
+ if (RecoveryInProgress())
+ elog(ERROR, "readonly mode not support access global temporary table");
+
+ if (rel->rd_rel->relkind == RELKIND_INDEX &&
+ rel->rd_index &&
+ (!rel->rd_index->indisvalid ||
+ !rel->rd_index->indisready ||
+ !rel->rd_index->indislive))
+ elog(ERROR, "invalid gtt index %s not allow to create storage", RelationGetRelationName(rel));
+
+ /* First time through: initialize the hash table */
+ if (gtt_storage_local_hash == NULL)
+ {
+#define GTT_LOCAL_HASH_SIZE 1024
+ HASHCTL ctl;
+
+ if (!CacheMemoryContext)
+ CreateCacheMemoryContext();
+
+ gtt_info_context =
+ AllocSetContextCreate(CacheMemoryContext,
+ "gtt info context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(Oid);
+ ctl.entrysize = sizeof(gtt_local_hash_entry);
+ ctl.hcxt = gtt_info_context;
+ gtt_storage_local_hash =
+ hash_create("global temporary table info",
+ GTT_LOCAL_HASH_SIZE,
+ &ctl, HASH_ELEM | HASH_BLOBS);
+ }
+
+ Assert(CacheMemoryContext);
+ Assert(gtt_info_context);
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ entry = gtt_search_by_relid(relid, true);
+ if (!entry)
+ {
+ bool found = false;
+
+ /* Look up or create an entry */
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &relid, HASH_ENTER, &found);
+
+ if (found)
+ {
+ MemoryContextSwitchTo(oldcontext);
+ elog(ERROR, "backend %d relid %u already exists in gtt local hash",
+ MyBackendId, relid);
+ }
+
+ entry->relfilenode_list = NIL;
+ entry->relkind = rel->rd_rel->relkind;
+ entry->on_commit_delete = false;
+ entry->oldrelid = InvalidOid;
+
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ /* record the on commit clause */
+ if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+ {
+ entry->on_commit_delete = true;
+ register_on_commit_action(RelationGetRelid(rel), ONCOMMIT_DELETE_ROWS);
+ }
+ }
+
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ {
+ gtt_storage_checkin(relid);
+ }
+ }
+
+ /* record storage info relstat columnstats and transaction info to relfilenode list */
+ new_node = palloc0(sizeof(gtt_relfilenode));
+ new_node->relfilenode = rnode.relNode;
+ new_node->spcnode = rnode.spcNode;
+ new_node->relpages = 0;
+ new_node->reltuples = 0;
+ new_node->relallvisible = 0;
+ new_node->relfrozenxid = InvalidTransactionId;
+ new_node->relminmxid = InvalidMultiXactId;
+ new_node->natts = 0;
+ new_node->attnum = NULL;
+ new_node->att_stat_tups = NULL;
+ entry->relfilenode_list = lappend(entry->relfilenode_list, new_node);
+
+ /* init column stats structure */
+ natts = RelationGetNumberOfAttributes(rel);
+ new_node->attnum = palloc0(sizeof(int) * natts);
+ new_node->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+ new_node->natts = natts;
+
+ /* only heap have transaction info */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ new_node->relfrozenxid = RecentXmin;
+ new_node->relminmxid = GetOldestMultiXactId();
+
+ /**/
+ insert_gtt_relfrozenxid_to_ordered_list(new_node->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Registration callbacks are used to trigger cleanup during process exit */
+ if (!gtt_cleaner_exit_registered)
+ {
+ before_shmem_exit(gtt_storage_removeall, 0);
+ gtt_cleaner_exit_registered = true;
+ }
+
+ return;
+}
+
+/*
+ * Remove GTT information from local hash when transaction commit/rollback.
+ */
+void
+forget_gtt_storage_info(Oid relid, RelFileNode rnode, bool isCommit)
+{
+ gtt_local_hash_entry *entry = NULL;
+ gtt_relfilenode *d_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt rel %u not found in local hash", relid);
+
+ return;
+ }
+
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, true);
+ if (d_rnode == NULL)
+ {
+ if (isCommit)
+ elog(ERROR,"gtt relfilenode %u not found in rel %u", rnode.relNode, relid);
+ else if (entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+ gtt_relfilenode *gttnode2 = NULL;
+
+ /*
+ * For cluster GTT rollback.
+ * We need to roll back the exchange relfilenode operation.
+ */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ gttnode2 = gtt_search_relfilenode(entry2, rnode.relNode, false);
+ Assert(gttnode2->relfilenode == rnode.relNode);
+ Assert(list_length(entry->relfilenode_list) == 1);
+ /* rollback switch relfilenode */
+ gtt_switch_rel_relfilenode(entry2->relid, gttnode2->relfilenode,
+ entry->relid, gtt_fetch_current_relfilenode(entry->relid),
+ false);
+ /* clean up footprint */
+ entry2->oldrelid = InvalidOid;
+
+ /* temp relfilenode need free */
+ d_rnode = gtt_search_relfilenode(entry, rnode.relNode, false);
+ Assert(d_rnode);
+ }
+ else
+ {
+ /* rollback transaction */
+ if (entry->relfilenode_list == NIL)
+ {
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+ }
+ }
+
+ /* Clean up transaction info from Local order list and MyProc */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ Assert(TransactionIdIsNormal(d_rnode->relfrozenxid) || !isCommit);
+
+ /* this is valid relfrozenxid */
+ if (TransactionIdIsValid(d_rnode->relfrozenxid))
+ {
+ remove_gtt_relfrozenxid_from_ordered_list(d_rnode->relfrozenxid);
+ set_gtt_session_relfrozenxid();
+ }
+ }
+
+ /* delete relfilenode from rel entry */
+ entry->relfilenode_list = list_delete_ptr(entry->relfilenode_list, d_rnode);
+ gtt_free_statistics(d_rnode);
+
+ if (entry->relfilenode_list == NIL)
+ {
+ /* this means we truncate this GTT at current backend */
+
+ /* tell shared hash that current backend will no longer use this GTT */
+ if (entry->relkind == RELKIND_RELATION ||
+ entry->relkind == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relid, false, isCommit);
+
+ if (isCommit && entry->oldrelid != InvalidOid)
+ {
+ gtt_local_hash_entry *entry2 = NULL;
+
+ /* commit transaction at cluster GTT, need clean up footprint */
+ entry2 = gtt_search_by_relid(entry->oldrelid, false);
+ entry2->oldrelid = InvalidOid;
+ }
+
+ hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_REMOVE, NULL);
+ }
+
+ return;
+}
+
+/*
+ * Check if current backend is using this GTT.
+ */
+bool
+gtt_storage_attached(Oid relid)
+{
+ bool found = false;
+ gtt_local_hash_entry *entry = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ if (!OidIsValid(relid))
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry)
+ found = true;
+
+ return found;
+}
+
+/*
+ * When backend exit, bulk cleaning all GTT storage and local buffer of this backend.
+ */
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+ HASH_SEQ_STATUS status;
+ gtt_local_hash_entry *entry;
+ SMgrRelation *srels = NULL;
+ Oid *relids = NULL;
+ char *relkinds = NULL;
+ int nrels = 0,
+ nfiles = 0,
+ maxrels = 0,
+ maxfiles = 0,
+ i = 0;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ /* Search all relfilenode for GTT in current backend */
+ hash_seq_init(&status, gtt_storage_local_hash);
+ while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+ {
+ ListCell *lc;
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ SMgrRelation srel;
+ RelFileNode rnode;
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+
+ rnode.spcNode = gtt_rnode->spcnode;
+ rnode.dbNode = MyDatabaseId;
+ rnode.relNode = gtt_rnode->relfilenode;
+ srel = smgropen(rnode, MyBackendId);
+
+ if (maxfiles == 0)
+ {
+ maxfiles = 32;
+ srels = palloc(sizeof(SMgrRelation) * maxfiles);
+ }
+ else if (maxfiles <= nfiles)
+ {
+ maxfiles *= 2;
+ srels = repalloc(srels, sizeof(SMgrRelation) * maxfiles);
+ }
+
+ srels[nfiles++] = srel;
+ }
+
+ if (maxrels == 0)
+ {
+ maxrels = 32;
+ relids = palloc(sizeof(Oid) * maxrels);
+ relkinds = palloc(sizeof(char) * maxrels);
+ }
+ else if (maxrels <= nrels)
+ {
+ maxrels *= 2;
+ relids = repalloc(relids , sizeof(Oid) * maxrels);
+ relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+ }
+
+ relkinds[nrels] = entry->relkind;
+ relids[nrels] = entry->relid;
+ nrels++;
+ }
+
+ /* drop local buffer and storage */
+ if (nfiles > 0)
+ {
+ smgrdounlinkall(srels, nfiles, false);
+ for (i = 0; i < nfiles; i++)
+ smgrclose(srels[i]);
+
+ pfree(srels);
+ }
+
+ if (nrels)
+ {
+ LWLockAcquire(>t_shared_ctl->lock, LW_EXCLUSIVE);
+ for (i = 0; i < nrels; i++)
+ {
+ /* tell shared hash */
+ if (relkinds[i] == RELKIND_RELATION ||
+ relkinds[i] == RELKIND_SEQUENCE)
+ gtt_storage_checkout(relids[i], true, false);
+ }
+ LWLockRelease(>t_shared_ctl->lock);
+
+ pfree(relids);
+ pfree(relkinds);
+ }
+
+ /* set to global area */
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId;
+
+ return;
+}
+
+/*
+ * Update GTT relstats(relpage/reltuple/relallvisible)
+ * to local hash.
+ */
+void
+up_gtt_relstats(Oid relid,
+ BlockNumber num_pages,
+ double num_tuples,
+ BlockNumber num_all_visible_pages,
+ TransactionId relfrozenxid,
+ TransactionId relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (!OidIsValid(relid))
+ return;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (num_pages > 0 &&
+ gtt_rnode->relpages != (int32)num_pages)
+ gtt_rnode->relpages = (int32)num_pages;
+
+ if (num_tuples > 0 &&
+ gtt_rnode->reltuples != (float4)num_tuples)
+ gtt_rnode->reltuples = (float4)num_tuples;
+
+ /* only heap contain transaction information and relallvisible */
+ if (entry->relkind == RELKIND_RELATION)
+ {
+ if (num_all_visible_pages > 0 &&
+ gtt_rnode->relallvisible != (int32)num_all_visible_pages)
+ {
+ gtt_rnode->relallvisible = (int32)num_all_visible_pages;
+ }
+
+ if (TransactionIdIsNormal(relfrozenxid) &&
+ gtt_rnode->relfrozenxid != relfrozenxid &&
+ (TransactionIdPrecedes(gtt_rnode->relfrozenxid, relfrozenxid) ||
+ TransactionIdPrecedes(ReadNewTransactionId(), gtt_rnode->relfrozenxid)))
+ {
+ /* set to local order list */
+ remove_gtt_relfrozenxid_from_ordered_list(gtt_rnode->relfrozenxid);
+ gtt_rnode->relfrozenxid = relfrozenxid;
+ insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+ /* set to global area */
+ set_gtt_session_relfrozenxid();
+ }
+
+ if (MultiXactIdIsValid(relminmxid) &&
+ gtt_rnode->relminmxid != relminmxid &&
+ (MultiXactIdPrecedes(gtt_rnode->relminmxid, relminmxid) ||
+ MultiXactIdPrecedes(ReadNextMultiXactId(), gtt_rnode->relminmxid)))
+ {
+ gtt_rnode->relminmxid = relminmxid;
+ }
+ }
+
+ return;
+}
+
+/*
+ * Search GTT relstats(relpage/reltuple/relallvisible)
+ * from local has.
+ */
+bool
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+ BlockNumber *relallvisible, TransactionId *relfrozenxid,
+ TransactionId *relminmxid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return false;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return false;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return false;
+
+ if (relpages)
+ *relpages = gtt_rnode->relpages;
+
+ if (reltuples)
+ *reltuples = gtt_rnode->reltuples;
+
+ if (relallvisible)
+ *relallvisible = gtt_rnode->relallvisible;
+
+ if (relfrozenxid)
+ *relfrozenxid = gtt_rnode->relfrozenxid;
+
+ if (relminmxid)
+ *relminmxid = gtt_rnode->relminmxid;
+
+ return true;
+}
+
+/*
+ * Update GTT info(definition is same as pg_statistic)
+ * to local hash.
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+ gtt_local_hash_entry *entry;
+ MemoryContext oldcontext;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return;
+
+ Assert(entry->relid == reloid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return;
+
+ if (gtt_rnode->natts < natts)
+ {
+ elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+ return;
+ }
+
+ /* switch context to gtt_info_context for store tuple at heap_form_tuple */
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == 0)
+ {
+ gtt_rnode->attnum[i] = attnum;
+ break;
+ }
+ else if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ heap_freetuple(gtt_rnode->att_stat_tups[i]);
+ gtt_rnode->att_stat_tups[i] = NULL;
+ break;
+ }
+ }
+
+ Assert(i < gtt_rnode->natts);
+ Assert(gtt_rnode->att_stat_tups[i] == NULL);
+ gtt_rnode->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Search GTT statistic info(definition is same as pg_statistic)
+ * from local hash.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+ gtt_local_hash_entry *entry;
+ int i = 0;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return NULL;
+
+ entry = gtt_search_by_relid(reloid, true);
+ if (entry == NULL)
+ return NULL;
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return NULL;
+
+ for (i = 0; i < gtt_rnode->natts; i++)
+ {
+ if (gtt_rnode->attnum[i] == attnum)
+ {
+ Assert(gtt_rnode->att_stat_tups[i]);
+ return gtt_rnode->att_stat_tups[i];
+ }
+ }
+
+ return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+ /* do nothing */
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Insert a RelfrozenXID into the list and keep the list in order.
+ */
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+ MemoryContext oldcontext;
+ ListCell *cell;
+ int i;
+
+ Assert(TransactionIdIsNormal(relfrozenxid));
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+
+ /* Does the datum belong at the front? */
+ if (gtt_session_relfrozenxid_list == NIL ||
+ TransactionIdFollowsOrEquals(relfrozenxid,
+ linitial_oid(gtt_session_relfrozenxid_list)))
+ {
+ gtt_session_relfrozenxid_list =
+ lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+ }
+
+ /* No, so find the entry it belongs after */
+ i = 0;
+ foreach (cell, gtt_session_relfrozenxid_list)
+ {
+ if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+ break;
+
+ i++;
+ }
+ gtt_session_relfrozenxid_list =
+ list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return;
+}
+
+/*
+ * Maintain a order relfrozenxid list of backend Level for GTT.
+ * Remove a RelfrozenXID from order list gtt_session_relfrozenxid_list.
+ */
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+ gtt_session_relfrozenxid_list =
+ list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+/*
+ * Update of backend Level oldest relfrozenxid to MyProc.
+ * This makes each backend's oldest RelFrozenxID globally visible.
+ */
+static void
+set_gtt_session_relfrozenxid(void)
+{
+ TransactionId gtt_frozenxid = InvalidTransactionId;
+
+ if (gtt_session_relfrozenxid_list)
+ gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+ gtt_session_frozenxid = gtt_frozenxid;
+ if (MyProc->backend_gtt_frozenxid != gtt_frozenxid)
+ MyProc->backend_gtt_frozenxid = gtt_frozenxid;
+}
+
+/*
+ * Get GTT column level data statistics.
+ */
+Datum
+pg_get_gtt_statistics(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ HeapTuple tuple;
+ Relation rel = NULL;
+ Oid reloid = PG_GETARG_OID(0);
+ int attnum = PG_GETARG_INT32(1);
+ char rel_persistence;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ Relation pg_tatistic = NULL;
+ TupleDesc sd;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not " \
+ "allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ pg_tatistic = relation_open(StatisticRelationId, AccessShareLock);
+ sd = RelationGetDescr(pg_tatistic);
+
+ /* get data from local hash */
+ 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);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+ relation_close(pg_tatistic, AccessShareLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get GTT table level data statistics.
+ */
+Datum
+pg_get_gtt_relstats(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ Oid relnode = 0;
+ char rel_persistence;
+ BlockNumber relpages = 0;
+ BlockNumber relallvisible = 0;
+ uint32 relfrozenxid = 0;
+ uint32 relminmxid = 0;
+ double reltuples = 0;
+ Relation rel = NULL;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ get_gtt_relstats(reloid,
+ &relpages, &reltuples, &relallvisible,
+ &relfrozenxid, &relminmxid);
+ relnode = gtt_fetch_current_relfilenode(reloid);
+ if (relnode != InvalidOid)
+ {
+ Datum values[6];
+ bool isnull[6];
+
+ memset(isnull, 0, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(relnode);
+ values[1] = Int32GetDatum(relpages);
+ values[2] = Float4GetDatum((float4)reltuples);
+ values[3] = Int32GetDatum(relallvisible);
+ values[4] = UInt32GetDatum(relfrozenxid);
+ values[5] = UInt32GetDatum(relminmxid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ tuplestore_donestoring(tupstore);
+
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get a list of backend pids that are currently using this GTT.
+ */
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ PGPROC *proc = NULL;
+ Bitmapset *map = NULL;
+ Tuplestorestate *tupstore;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ Oid reloid = PG_GETARG_OID(0);
+ char rel_persistence;
+ Relation rel = NULL;
+ pid_t pid = 0;
+ int backendid = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+ if (!(rsinfo->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ oldcontext = MemoryContextSwitchTo(
+ rsinfo->econtext->ecxt_per_query_memory);
+ tupstore = tuplestore_begin_heap(true, false, work_mem);
+ rsinfo->returnMode = SFRM_Materialize;
+ rsinfo->setResult = tupstore;
+ rsinfo->setDesc = tupdesc;
+ MemoryContextSwitchTo(oldcontext);
+
+ rel = relation_open(reloid, AccessShareLock);
+ rel_persistence = get_rel_persistence(reloid);
+ if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ elog(WARNING, "relation OID %u is not a global temporary table", reloid);
+ relation_close(rel, NoLock);
+ return (Datum) 0;
+ }
+
+ /* get data from share hash */
+ map = copy_active_gtt_bitmap(reloid);
+ if (map)
+ {
+ backendid = bms_first_member(map);
+
+ do
+ {
+ /* backendid map to process pid */
+ proc = BackendIdGetProc(backendid);
+ pid = proc->pid;
+ if (pid > 0)
+ {
+ Datum values[2];
+ bool isnull[2];
+
+ memset(isnull, false, sizeof(isnull));
+ memset(values, 0, sizeof(values));
+ values[0] = UInt32GetDatum(reloid);
+ values[1] = Int32GetDatum(pid);
+ tuple = heap_form_tuple(tupdesc, values, isnull);
+ tuplestore_puttuple(tupstore, tuple);
+ }
+ backendid = bms_next_member(map, backendid);
+ } while (backendid > 0);
+
+ pfree(map);
+ }
+
+ tuplestore_donestoring(tupstore);
+ relation_close(rel, NoLock);
+
+ return (Datum) 0;
+}
+
+/*
+ * Get backend level oldest relfrozenxid of each backend using GTT in current database.
+ */
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ Tuplestorestate *tupstore;
+ int *pids = NULL;
+ uint32 *xids = NULL;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+ HeapTuple tuple;
+ int num_xid = MaxBackends + 1;
+ 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);
+
+ /* Get backend level oldest relfrozenxid in all backend that in MyDatabaseId use GTT */
+ oldest = list_all_backend_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;
+}
+
+/*
+ * In order to build the GTT index, force enable GTT'index.
+ */
+void
+force_enable_gtt_index(Relation index)
+{
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ Assert(index->rd_rel->relkind == RELKIND_INDEX);
+ Assert(OidIsValid(RelationGetRelid(index)));
+
+ index->rd_index->indisvalid = true;
+ index->rd_index->indislive = true;
+ index->rd_index->indisready = true;
+}
+
+/*
+ * Fix the local state of the GTT's index.
+ */
+void
+gtt_fix_index_backend_state(Relation index)
+{
+ Oid indexOid = RelationGetRelid(index);
+ Oid heapOid = index->rd_index->indrelid;
+
+ /* Must be GTT */
+ if (!RELATION_IS_GLOBAL_TEMP(index))
+ return;
+
+ if (!index->rd_index->indisvalid)
+ return;
+
+ /*
+ * If this GTT is not initialized in the current backend,
+ * its index status is temporarily set to invalid(local relcache).
+ */
+ if (gtt_storage_attached(heapOid) &&
+ !gtt_storage_attached(indexOid))
+ {
+ index->rd_index->indisvalid = false;
+ index->rd_index->indislive = false;
+ index->rd_index->indisready = false;
+ }
+
+ return;
+}
+
+/*
+ * During the SQL initialization of the executor (InitPlan)
+ * Initialize storage of GTT GTT'indexes and build empty index.
+ */
+void
+init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo)
+{
+ Relation relation = resultRelInfo->ri_RelationDesc;
+ int i;
+ Oid toastrelid;
+
+ if (!(operation == CMD_UTILITY || operation == CMD_INSERT))
+ return;
+
+ if (!RELKIND_HAS_STORAGE(relation->rd_rel->relkind))
+ return;
+
+ if (!RELATION_IS_GLOBAL_TEMP(relation))
+ return;
+
+ /* Each GTT is initialized once in each backend */
+ if (gtt_storage_attached(RelationGetRelid(relation)))
+ return;
+
+ /* init heap storage */
+ RelationCreateStorage(relation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, relation);
+
+ 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);
+
+ /* init index storage */
+ 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);
+ /* build empty index */
+ indexInfo = BuildDummyIndexInfo(currentIndex);
+ index_build(toastrel, currentIndex, indexInfo, true, false);
+ index_close(currentIndex, NoLock);
+ }
+
+ table_close(toastrel, NoLock);
+ }
+
+ return;
+}
+
+/*
+ * Release the data structure memory used to store GTT storage info.
+ */
+static void
+gtt_free_statistics(gtt_relfilenode *rnode)
+{
+ int i;
+
+ Assert(rnode);
+
+ for (i = 0; i < rnode->natts; i++)
+ {
+ if (rnode->att_stat_tups[i])
+ {
+ heap_freetuple(rnode->att_stat_tups[i]);
+ rnode->att_stat_tups[i] = NULL;
+ }
+ }
+
+ if (rnode->attnum)
+ pfree(rnode->attnum);
+
+ if (rnode->att_stat_tups)
+ pfree(rnode->att_stat_tups);
+
+ pfree(rnode);
+
+ return;
+}
+
+/*
+ * Get the current relfilenode of this GTT.
+ */
+Oid
+gtt_fetch_current_relfilenode(Oid relid)
+{
+ gtt_local_hash_entry *entry;
+ gtt_relfilenode *gtt_rnode = NULL;
+
+ if (max_active_gtt <= 0)
+ return InvalidOid;
+
+ entry = gtt_search_by_relid(relid, true);
+ if (entry == NULL)
+ return InvalidOid;
+
+ Assert(entry->relid == relid);
+
+ gtt_rnode = lfirst(list_tail(entry->relfilenode_list));
+ if (gtt_rnode == NULL)
+ return InvalidOid;
+
+ return gtt_rnode->relfilenode;
+}
+
+/*
+ * For cluster GTT.
+ * Exchange new and old relfilenode, leave footprints ensure rollback capability.
+ */
+void
+gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint)
+{
+ gtt_local_hash_entry *entry1;
+ gtt_local_hash_entry *entry2;
+ gtt_relfilenode *gtt_rnode1 = NULL;
+ gtt_relfilenode *gtt_rnode2 = NULL;
+ MemoryContext oldcontext;
+
+ if (max_active_gtt <= 0)
+ return;
+
+ if (gtt_storage_local_hash == NULL)
+ return;
+
+ entry1 = gtt_search_by_relid(rel1, false);
+ gtt_rnode1 = gtt_search_relfilenode(entry1, relfilenode1, false);
+
+ entry2 = gtt_search_by_relid(rel2, false);
+ gtt_rnode2 = gtt_search_relfilenode(entry2, relfilenode2, false);
+
+ oldcontext = MemoryContextSwitchTo(gtt_info_context);
+ entry1->relfilenode_list = list_delete_ptr(entry1->relfilenode_list, gtt_rnode1);
+ entry2->relfilenode_list = lappend(entry2->relfilenode_list, gtt_rnode1);
+
+ entry2->relfilenode_list = list_delete_ptr(entry2->relfilenode_list, gtt_rnode2);
+ entry1->relfilenode_list = lappend(entry1->relfilenode_list, gtt_rnode2);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (footprint)
+ {
+ entry1->oldrelid = rel2;
+ entry2->oldrelid = rel1;
+ }
+
+ return;
+}
+
+/*
+ * Get a relfilenode used by this GTT during the transaction life cycle.
+ */
+static gtt_relfilenode *
+gtt_search_relfilenode(gtt_local_hash_entry *entry, Oid relfilenode, bool missing_ok)
+{
+ gtt_relfilenode *rnode = NULL;
+ ListCell *lc;
+
+ Assert(entry);
+
+ foreach(lc, entry->relfilenode_list)
+ {
+ gtt_relfilenode *gtt_rnode = lfirst(lc);
+ if (gtt_rnode->relfilenode == relfilenode)
+ {
+ rnode = gtt_rnode;
+ break;
+ }
+ }
+
+ if (!missing_ok && rnode == NULL)
+ elog(ERROR, "find relfilenode %u relfilenodelist from relid %u fail", relfilenode, entry->relid);
+
+ return rnode;
+}
+
+/*
+ * Get one GTT info from local hash.
+ */
+static gtt_local_hash_entry *
+gtt_search_by_relid(Oid relid, bool missing_ok)
+{
+ gtt_local_hash_entry *entry = NULL;
+
+ if (gtt_storage_local_hash == NULL)
+ return NULL;
+
+ entry = hash_search(gtt_storage_local_hash,
+ (void *) &(relid), HASH_FIND, NULL);
+
+ if (entry == NULL && !missing_ok)
+ elog(ERROR, "relid %u not found in local hash", relid);
+
+ return entry;
+}
+
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index fa58afd..99fe8f9 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -186,6 +186,91 @@ CREATE OR REPLACE VIEW pg_sequences AS
WHERE NOT pg_is_other_temp_schema(N.oid)
AND relkind = 'S';
+-- For global temporary table
+CREATE VIEW pg_gtt_relstats WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_relstats(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_attached_pids WITH (security_barrier) AS
+ SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.*
+ FROM
+ pg_class c
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_gtt_attached_pid(c.oid) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','S') AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
+CREATE VIEW pg_gtt_stats WITH (security_barrier) AS
+SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stavalues1
+ WHEN s.stakind2 = 1 THEN s.stavalues2
+ WHEN s.stakind3 = 1 THEN s.stavalues3
+ WHEN s.stakind4 = 1 THEN s.stavalues4
+ WHEN s.stakind5 = 1 THEN s.stavalues5
+ END AS most_common_vals,
+ CASE
+ WHEN s.stakind1 = 1 THEN s.stanumbers1
+ WHEN s.stakind2 = 1 THEN s.stanumbers2
+ WHEN s.stakind3 = 1 THEN s.stanumbers3
+ WHEN s.stakind4 = 1 THEN s.stanumbers4
+ WHEN s.stakind5 = 1 THEN s.stanumbers5
+ END AS most_common_freqs,
+ CASE
+ WHEN s.stakind1 = 2 THEN s.stavalues1
+ WHEN s.stakind2 = 2 THEN s.stavalues2
+ WHEN s.stakind3 = 2 THEN s.stavalues3
+ WHEN s.stakind4 = 2 THEN s.stavalues4
+ WHEN s.stakind5 = 2 THEN s.stavalues5
+ END AS histogram_bounds,
+ CASE
+ WHEN s.stakind1 = 3 THEN s.stanumbers1[1]
+ WHEN s.stakind2 = 3 THEN s.stanumbers2[1]
+ WHEN s.stakind3 = 3 THEN s.stanumbers3[1]
+ WHEN s.stakind4 = 3 THEN s.stanumbers4[1]
+ WHEN s.stakind5 = 3 THEN s.stanumbers5[1]
+ END AS correlation,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stavalues1
+ WHEN s.stakind2 = 4 THEN s.stavalues2
+ WHEN s.stakind3 = 4 THEN s.stavalues3
+ WHEN s.stakind4 = 4 THEN s.stavalues4
+ WHEN s.stakind5 = 4 THEN s.stavalues5
+ END AS most_common_elems,
+ CASE
+ WHEN s.stakind1 = 4 THEN s.stanumbers1
+ WHEN s.stakind2 = 4 THEN s.stanumbers2
+ WHEN s.stakind3 = 4 THEN s.stanumbers3
+ WHEN s.stakind4 = 4 THEN s.stanumbers4
+ WHEN s.stakind5 = 4 THEN s.stanumbers5
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN s.stakind1 = 5 THEN s.stanumbers1
+ WHEN s.stakind2 = 5 THEN s.stanumbers2
+ WHEN s.stakind3 = 5 THEN s.stanumbers3
+ WHEN s.stakind4 = 5 THEN s.stanumbers4
+ WHEN s.stakind5 = 5 THEN s.stanumbers5
+ END AS elem_count_histogram
+ FROM
+ pg_class c
+ JOIN pg_attribute a ON c.oid = a.attrelid
+ LEFT JOIN pg_namespace n ON n.oid = c.relnamespace,
+ pg_get_gtt_statistics(c.oid, a.attnum, ''::text) as s
+ WHERE c.relpersistence='g' AND c.relkind in('r','p','i','t') and NOT a.attisdropped AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
+
CREATE VIEW pg_stats WITH (security_barrier) AS
SELECT
nspname AS schemaname,
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7295cf0..79830d0 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);
@@ -185,6 +186,17 @@ analyze_rel(Oid relid, RangeVar *relation,
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(onerel)))
+ {
+ relation_close(onerel, ShareUpdateExclusiveLock);
+ return;
+ }
+
+ /*
* We can ANALYZE any table except pg_statistic. See update_attstats
*/
if (RelationGetRelid(onerel) == StatisticRelationId)
@@ -575,14 +587,15 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
* pg_statistic for columns we didn't process, we leave them alone.)
*/
update_attstats(RelationGetRelid(onerel), inh,
- attr_cnt, vacattrstats);
+ attr_cnt, vacattrstats, RelationGetRelPersistence(onerel));
for (ind = 0; ind < nindexes; ind++)
{
AnlIndexData *thisdata = &indexdata[ind];
update_attstats(RelationGetRelid(Irel[ind]), false,
- thisdata->attr_cnt, thisdata->vacattrstats);
+ thisdata->attr_cnt, thisdata->vacattrstats,
+ RelationGetRelPersistence(Irel[ind]));
}
/*
@@ -1445,7 +1458,7 @@ acquire_inherited_sample_rows(Relation onerel, int elevel,
* by taking a self-exclusive lock on the relation in analyze_rel().
*/
static void
-update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
+update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats, char relpersistence)
{
Relation sd;
int attno;
@@ -1547,31 +1560,48 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
}
}
- /* Is there already a pg_statistic tuple for this attribute? */
- oldtup = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(relid),
- Int16GetDatum(stats->attr->attnum),
- BoolGetDatum(inh));
-
- if (HeapTupleIsValid(oldtup))
+ /*
+ * For global temporary table,
+ * Update column statistic to localhash, not 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 096a06f..f75cb42 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -33,6 +33,7 @@
#include "catalog/namespace.h"
#include "catalog/objectaccess.h"
#include "catalog/pg_am.h"
+#include "catalog/storage_gtt.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/defrem.h"
@@ -73,6 +74,12 @@ static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
bool verbose, bool *pSwapToastByContent,
TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
static List *get_tables_to_cluster(MemoryContext cluster_context);
+static void gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables);
/*---------------------------------------------------------------------------
@@ -391,6 +398,18 @@ cluster_rel(Oid tableOid, Oid indexOid, ClusterParams *params)
}
/*
+ * Skip the global temporary table that did not initialize the storage
+ * in this backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap) &&
+ !gtt_storage_attached(RelationGetRelid(OldHeap)))
+ {
+ relation_close(OldHeap, AccessExclusiveLock);
+ pgstat_progress_end_command();
+ return;
+ }
+
+ /*
* Also check for active uses of the relation in the current transaction,
* including open scans and pending AFTER trigger events.
*/
@@ -774,6 +793,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
BlockNumber num_pages;
int elevel = verbose ? INFO : DEBUG2;
PGRUsage ru0;
+ bool is_gtt = false;
+ uint32 gtt_relfrozenxid = 0;
+ uint32 gtt_relminmxid = 0;
pg_rusage_init(&ru0);
@@ -787,6 +809,9 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
else
OldIndex = NULL;
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ is_gtt = true;
+
/*
* Their tuple descriptors should be exactly alike, but here we only need
* assume that they have the same number of columns.
@@ -854,20 +879,38 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
&OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
NULL);
- /*
- * FreezeXid will become the table's new relfrozenxid, and that mustn't go
- * backwards, so take the max.
- */
- if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
- TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
- FreezeXid = OldHeap->rd_rel->relfrozenxid;
+ if (is_gtt)
+ {
+ /* Gets transaction information for global temporary table from localhash. */
+ get_gtt_relstats(OIDOldHeap,
+ NULL, NULL, NULL,
+ >t_relfrozenxid, >t_relminmxid);
+
+ if (TransactionIdIsValid(gtt_relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, gtt_relfrozenxid))
+ FreezeXid = gtt_relfrozenxid;
+
+ if (MultiXactIdIsValid(gtt_relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, gtt_relminmxid))
+ MultiXactCutoff = gtt_relminmxid;
+ }
+ else
+ {
+ /*
+ * FreezeXid will become the table's new relfrozenxid, and that mustn't go
+ * backwards, so take the max.
+ */
+ if (TransactionIdIsValid(OldHeap->rd_rel->relfrozenxid) &&
+ TransactionIdPrecedes(FreezeXid, OldHeap->rd_rel->relfrozenxid))
+ FreezeXid = OldHeap->rd_rel->relfrozenxid;
- /*
- * MultiXactCutoff, similarly, shouldn't go backwards either.
- */
- if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
- MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
- MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ /*
+ * MultiXactCutoff, similarly, shouldn't go backwards either.
+ */
+ if (MultiXactIdIsValid(OldHeap->rd_rel->relminmxid) &&
+ MultiXactIdPrecedes(MultiXactCutoff, OldHeap->rd_rel->relminmxid))
+ MultiXactCutoff = OldHeap->rd_rel->relminmxid;
+ }
/*
* Decide whether to use an indexscan or seqscan-and-optional-sort to scan
@@ -935,6 +978,15 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
table_close(OldHeap, NoLock);
table_close(NewHeap, NoLock);
+ /* Update relstats of global temporary table to localhash. */
+ if (is_gtt)
+ {
+ up_gtt_relstats(RelationGetRelid(NewHeap), num_pages, num_tuples, 0,
+ InvalidTransactionId, InvalidMultiXactId);
+ CommandCounterIncrement();
+ return;
+ }
+
/* Update pg_class to reflect the correct values of pages and tuples. */
relRelation = table_open(RelationRelationId, RowExclusiveLock);
@@ -1371,10 +1423,22 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
* Swap the contents of the heap relations (including any toast tables).
* Also set old heap's relfrozenxid to frozenXid.
*/
- swap_relation_files(OIDOldHeap, OIDNewHeap,
+ if (newrelpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(!is_system_catalog);
+ /* For global temporary table modify data in localhash, not pg_class */
+ gtt_swap_relation_files(OIDOldHeap, OIDNewHeap,
+ (OIDOldHeap == RelationRelationId),
+ swap_toast_by_content, is_internal,
+ frozenXid, cutoffMulti, mapped_tables);
+ }
+ else
+ {
+ swap_relation_files(OIDOldHeap, OIDNewHeap,
(OIDOldHeap == RelationRelationId),
swap_toast_by_content, is_internal,
frozenXid, cutoffMulti, mapped_tables);
+ }
/*
* If it's a system catalog, queue a sinval message to flush all catcaches
@@ -1582,3 +1646,146 @@ get_tables_to_cluster(MemoryContext cluster_context)
return rvs;
}
+
+/*
+ * For global temporary table, storage information is stored in localhash,
+ * This function like swap_relation_files except that update storage information,
+ * in the localhash, not pg_class.
+ */
+static void
+gtt_swap_relation_files(Oid r1, Oid r2, bool target_is_pg_class,
+ bool swap_toast_by_content,
+ bool is_internal,
+ TransactionId frozenXid,
+ MultiXactId cutoffMulti,
+ Oid *mapped_tables)
+{
+ Relation relRelation;
+ Oid relfilenode1,
+ relfilenode2;
+ Relation rel1;
+ Relation rel2;
+
+ relRelation = table_open(RelationRelationId, RowExclusiveLock);
+
+ rel1 = relation_open(r1, AccessExclusiveLock);
+ rel2 = relation_open(r2, AccessExclusiveLock);
+
+ relfilenode1 = gtt_fetch_current_relfilenode(r1);
+ relfilenode2 = gtt_fetch_current_relfilenode(r2);
+
+ Assert(OidIsValid(relfilenode1) && OidIsValid(relfilenode2));
+ gtt_switch_rel_relfilenode(r1, relfilenode1, r2, relfilenode2, true);
+
+ CacheInvalidateRelcache(rel1);
+ CacheInvalidateRelcache(rel2);
+
+ InvokeObjectPostAlterHookArg(RelationRelationId, r1, 0,
+ InvalidOid, is_internal);
+ InvokeObjectPostAlterHookArg(RelationRelationId, r2, 0,
+ InvalidOid, true);
+
+ if (rel1->rd_rel->reltoastrelid || rel2->rd_rel->reltoastrelid)
+ {
+ if (swap_toast_by_content)
+ {
+ if (rel1->rd_rel->reltoastrelid && rel2->rd_rel->reltoastrelid)
+ {
+ gtt_swap_relation_files(rel1->rd_rel->reltoastrelid,
+ rel2->rd_rel->reltoastrelid,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ frozenXid,
+ cutoffMulti,
+ mapped_tables);
+ }
+ else
+ elog(ERROR, "cannot swap toast files by content when there's only one");
+ }
+ else
+ {
+ ObjectAddress baseobject,
+ toastobject;
+ long count;
+
+ if (IsSystemRelation(rel1))
+ elog(ERROR, "cannot swap toast files by links for system catalogs");
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel1->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ count = deleteDependencyRecordsFor(RelationRelationId,
+ rel2->rd_rel->reltoastrelid,
+ false);
+ if (count != 1)
+ elog(ERROR, "expected one dependency record for TOAST table, found %ld",
+ count);
+ }
+
+ /* Register new dependencies */
+ baseobject.classId = RelationRelationId;
+ baseobject.objectSubId = 0;
+ toastobject.classId = RelationRelationId;
+ toastobject.objectSubId = 0;
+
+ if (rel1->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r1;
+ toastobject.objectId = rel1->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+
+ if (rel2->rd_rel->reltoastrelid)
+ {
+ baseobject.objectId = r2;
+ toastobject.objectId = rel2->rd_rel->reltoastrelid;
+ recordDependencyOn(&toastobject, &baseobject,
+ DEPENDENCY_INTERNAL);
+ }
+ }
+ }
+
+ if (swap_toast_by_content &&
+ rel1->rd_rel->relkind == RELKIND_TOASTVALUE &&
+ rel2->rd_rel->relkind == RELKIND_TOASTVALUE)
+ {
+ Oid toastIndex1,
+ toastIndex2;
+
+ /* Get valid index for each relation */
+ toastIndex1 = toast_get_valid_index(r1,
+ AccessExclusiveLock);
+ toastIndex2 = toast_get_valid_index(r2,
+ AccessExclusiveLock);
+
+ gtt_swap_relation_files(toastIndex1,
+ toastIndex2,
+ target_is_pg_class,
+ swap_toast_by_content,
+ is_internal,
+ InvalidTransactionId,
+ InvalidMultiXactId,
+ mapped_tables);
+ }
+
+ relation_close(rel1, NoLock);
+ relation_close(rel2, NoLock);
+
+ table_close(relRelation, RowExclusiveLock);
+
+ RelationCloseSmgrByOid(r1);
+ RelationCloseSmgrByOid(r2);
+
+ CommandCounterIncrement();
+}
+
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index 8c712c8..0a35f6f 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -290,7 +290,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt,
Assert(rel);
/* check read-only transaction and parallel mode */
- if (XactReadOnly && !rel->rd_islocaltemp)
+ if (XactReadOnly && !RELATION_IS_TEMP_ON_CURRENT_SESSION(rel))
PreventCommandIfReadOnly("COPY FROM");
cstate = BeginCopyFrom(pstate, rel, whereClause,
diff --git a/src/backend/commands/copyfrom.c b/src/backend/commands/copyfrom.c
index c39cc73..8ce49e2 100644
--- a/src/backend/commands/copyfrom.c
+++ b/src/backend/commands/copyfrom.c
@@ -23,6 +23,7 @@
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
+#include "catalog/storage_gtt.h"
#include "commands/copy.h"
#include "commands/copyfrom_internal.h"
#include "commands/progress.h"
@@ -657,6 +658,9 @@ CopyFrom(CopyFromState cstate)
ExecOpenIndices(resultRelInfo, false);
+ /* Check and init global temporary table storage in current backend */
+ init_gtt_storage(CMD_INSERT, resultRelInfo);
+
/*
* Set up a ModifyTableState so we can let FDW(s) init themselves for
* foreign-table result relation(s).
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index f9f3ff3..db2aa0a 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -570,7 +570,7 @@ DefineIndex(Oid relationId,
* is more efficient. Do this before any use of the concurrent option is
* done.
*/
- if (stmt->concurrent && get_rel_persistence(relationId) != RELPERSISTENCE_TEMP)
+ if (stmt->concurrent && !RelpersistenceTsTemp(get_rel_persistence(relationId)))
concurrent = true;
else
concurrent = false;
@@ -2572,7 +2572,7 @@ ReindexIndex(RangeVar *indexRelation, ReindexParams *params, bool isTopLevel)
if (relkind == RELKIND_PARTITIONED_INDEX)
ReindexPartitions(indOid, params, isTopLevel);
else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
- persistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(persistence))
ReindexRelationConcurrently(indOid, params);
else
{
@@ -2681,7 +2681,7 @@ ReindexTable(RangeVar *relation, ReindexParams *params, bool isTopLevel)
if (get_rel_relkind(heapOid) == RELKIND_PARTITIONED_TABLE)
ReindexPartitions(heapOid, params, isTopLevel);
else if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
- get_rel_persistence(heapOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(heapOid)))
{
result = ReindexRelationConcurrently(heapOid, params);
@@ -3043,7 +3043,7 @@ ReindexMultipleInternal(List *relids, ReindexParams *params)
relkind != RELKIND_PARTITIONED_TABLE);
if ((params->options & REINDEXOPT_CONCURRENTLY) != 0 &&
- relpersistence != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(relpersistence))
{
ReindexParams newparams = *params;
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index 34f2270..b8ea2ef 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -57,7 +57,10 @@ LockTableCommand(LockStmt *lockstmt)
RangeVarCallbackForLockTable,
(void *) &lockstmt->mode);
- if (get_rel_relkind(reloid) == RELKIND_VIEW)
+ /* Lock table command ignores global temporary table. */
+ if (get_rel_persistence(reloid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+ else if (get_rel_relkind(reloid) == RELKIND_VIEW)
LockViewRecurse(reloid, lockstmt->mode, lockstmt->nowait, NIL);
else if (recurse)
LockTableRecurse(reloid, lockstmt->mode, lockstmt->nowait);
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 0415df9..39f82bc 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"
@@ -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);
/*
@@ -275,8 +278,6 @@ ResetSequence(Oid seq_relid)
Buffer buf;
HeapTupleData seqdatatuple;
HeapTuple tuple;
- HeapTuple pgstuple;
- Form_pg_sequence pgsform;
int64 startv;
/*
@@ -287,12 +288,7 @@ ResetSequence(Oid seq_relid)
init_sequence(seq_relid, &elm, &seq_rel);
(void) read_seq_tuple(seq_rel, &buf, &seqdatatuple);
- pgstuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seq_relid));
- if (!HeapTupleIsValid(pgstuple))
- elog(ERROR, "cache lookup failed for sequence %u", seq_relid);
- pgsform = (Form_pg_sequence) GETSTRUCT(pgstuple);
- startv = pgsform->seqstart;
- ReleaseSysCache(pgstuple);
+ startv = get_seqence_start_value(seq_relid);
/*
* Copy the existing sequence tuple.
@@ -451,6 +447,15 @@ AlterSequence(ParseState *pstate, AlterSeqStmt *stmt)
init_sequence(relid, &elm, &seqrel);
+ if (RELATION_IS_GLOBAL_TEMP(seqrel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(seqrel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary sequence %s when other backend attached it.",
+ RelationGetRelationName(seqrel))));
+ }
+
rel = table_open(SequenceRelationId, RowExclusiveLock);
seqtuple = SearchSysCacheCopy1(SEQRELID,
ObjectIdGetDatum(relid));
@@ -611,7 +616,7 @@ nextval_internal(Oid relid, bool check_permissions)
RelationGetRelationName(seqrel))));
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("nextval()");
/*
@@ -936,7 +941,7 @@ do_setval(Oid relid, int64 next, bool iscalled)
ReleaseSysCache(pgstuple);
/* read-only transactions may only modify temp sequences */
- if (!seqrel->rd_islocaltemp)
+ if (!RELATION_IS_TEMP_ON_CURRENT_SESSION(seqrel))
PreventCommandIfReadOnly("setval()");
/*
@@ -1153,6 +1158,14 @@ init_sequence(Oid relid, SeqTable *p_elm, Relation *p_rel)
/* Return results */
*p_elm = elm;
*p_rel = seqrel;
+
+ /* Initializes the storage for sequence which the global temporary table belongs. */
+ if (RELATION_IS_GLOBAL_TEMP(seqrel) &&
+ !gtt_storage_attached(RelationGetRelid(seqrel)))
+ {
+ RelationCreateStorage(seqrel->rd_node, RELPERSISTENCE_GLOBAL_TEMP, seqrel);
+ gtt_init_seq(seqrel);
+ }
}
@@ -1953,3 +1966,51 @@ seq_mask(char *page, BlockNumber blkno)
mask_unused_space(page);
}
+
+/*
+ * Get the startValue of the sequence from syscache.
+ */
+int64
+get_seqence_start_value(Oid seqid)
+{
+ HeapTuple seqtuple;
+ Form_pg_sequence seqform;
+ int64 start;
+
+ seqtuple = SearchSysCache1(SEQRELID, ObjectIdGetDatum(seqid));
+ if (!HeapTupleIsValid(seqtuple))
+ elog(ERROR, "cache lookup failed for sequence %u",
+ seqid);
+
+ seqform = (Form_pg_sequence) GETSTRUCT(seqtuple);
+ start = seqform->seqstart;
+ ReleaseSysCache(seqtuple);
+
+ return start;
+}
+
+/*
+ * Initialize sequence which global temporary table belongs.
+ */
+void
+gtt_init_seq(Relation rel)
+{
+ 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);
+ heap_freetuple(tuple);
+
+ return;
+}
+
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 420991e..84d4e1f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -45,6 +45,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"
@@ -559,6 +560,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
static List *GetParentedForeignKeyRefs(Relation partition);
static void ATDetachCheckNoForeignKeyRefs(Relation partition);
static void ATExecAlterCollationRefreshVersion(Relation rel, List *coll);
+static OnCommitAction gtt_oncommit_option(List *options);
/* ----------------------------------------------------------------
@@ -604,6 +606,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
@@ -615,7 +618,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* Check consistency of arguments
*/
if (stmt->oncommit != ONCOMMIT_NOOP
- && stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+ && !RelpersistenceTsTemp(stmt->relation->relpersistence))
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("ON COMMIT can only be used on temporary tables")));
@@ -645,7 +648,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
* code. This is needed because calling code might not expect untrusted
* tables to appear in pg_temp at the front of its search path.
*/
- if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+ if (RelpersistenceTsTemp(stmt->relation->relpersistence)
&& InSecurityRestrictedOperation())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -746,6 +749,56 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
/*
* Parse and validate reloptions, if any.
*/
+ /* For global temporary table */
+ oncommit_action = gtt_oncommit_option(stmt->options);
+ if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+ (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);
+ }
+
+ /* Check oncommit clause and save to reloptions */
+ if (oncommit_action != ONCOMMIT_NOOP)
+ {
+ if (stmt->oncommit != ONCOMMIT_NOOP)
+ elog(ERROR, "could not create global temporary table with on commit and with clause at same time");
+
+ stmt->oncommit = oncommit_action;
+ }
+ else
+ {
+ DefElem *opt = makeNode(DefElem);
+
+ opt->type = T_DefElem;
+ opt->defnamespace = NULL;
+ opt->defname = "on_commit_delete_rows";
+ opt->defaction = DEFELEM_UNSPEC;
+
+ /* use reloptions to remember on commit clause */
+ if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+ opt->arg = (Node *)makeString("true");
+ else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+ opt->arg = (Node *)makeString("false");
+ else if (stmt->oncommit == ONCOMMIT_NOOP)
+ opt->arg = (Node *)makeString("false");
+ else
+ elog(ERROR, "global temporary 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 temporary table, which cannot be specified by a regular table");
+
reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
true, false);
@@ -1367,7 +1420,7 @@ RemoveRelations(DropStmt *drop)
* relation persistence cannot be known without its OID.
*/
if (drop->concurrent &&
- get_rel_persistence(relOid) != RELPERSISTENCE_TEMP)
+ !RelpersistenceTsTemp(get_rel_persistence(relOid)))
{
Assert(list_length(drop->objects) == 1 &&
drop->removeType == OBJECT_INDEX);
@@ -1573,7 +1626,16 @@ ExecuteTruncate(TruncateStmt *stmt)
Relation rel;
bool recurse = rv->inh;
Oid myrelid;
- LOCKMODE lockmode = AccessExclusiveLock;
+ LOCKMODE lockmode;
+
+ /*
+ * Truncate global temp table only cleans up the data in current backend,
+ * only low-level locks are required.
+ */
+ if (rv->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ lockmode = RowExclusiveLock;
+ else
+ lockmode = AccessExclusiveLock;
myrelid = RangeVarGetRelidExtended(rv, lockmode,
0, RangeVarCallbackForTruncate,
@@ -1838,6 +1900,14 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
continue;
/*
+ * Skip the global temporary table that is not initialized for storage
+ * in current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(rel) &&
+ !gtt_storage_attached(RelationGetRelid(rel)))
+ continue;
+
+ /*
* Normally, we need a transaction-safe truncation here. However, if
* the table was either created in the current (sub)transaction or has
* a new relfilenode in the current (sub)transaction, then we can just
@@ -3789,6 +3859,16 @@ AlterTable(AlterTableStmt *stmt, LOCKMODE lockmode,
/* Caller is required to provide an adequate lock. */
rel = relation_open(context->relid, NoLock);
+ /* We allow to alter global temporary table only current backend use it */
+ if (RELATION_IS_GLOBAL_TEMP(rel))
+ {
+ if (is_other_backend_use_gtt(RelationGetRelid(rel)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST),
+ errmsg("cannot alter global temporary table %s when other backend attached it.",
+ RelationGetRelationName(rel))));
+ }
+
CheckTableNotInUse(rel, "ALTER TABLE");
ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode, context);
@@ -5112,6 +5192,42 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
+ if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+ {
+ if (tab->chgPersistence)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot change global temporary table persistence setting")));
+
+ /*
+ * The storage for the global temporary table needs to be initialized
+ * before rewrite table.
+ */
+ if(!gtt_storage_attached(tab->relid))
+ {
+ ResultRelInfo *resultRelInfo;
+ MemoryContext oldcontext;
+ MemoryContext ctx_alter_gtt;
+
+ ctx_alter_gtt = AllocSetContextCreate(CurrentMemoryContext,
+ "gtt alter table", ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(ctx_alter_gtt);
+
+ resultRelInfo = makeNode(ResultRelInfo);
+ InitResultRelInfo(resultRelInfo, OldHeap,
+ 1, NULL, 0);
+ if (resultRelInfo->ri_RelationDesc->rd_rel->relhasindex &&
+ resultRelInfo->ri_IndexRelationDescs == NULL)
+ ExecOpenIndices(resultRelInfo, false);
+
+ init_gtt_storage(CMD_UTILITY, resultRelInfo);
+ ExecCloseIndices(resultRelInfo);
+
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(ctx_alter_gtt);
+ }
+ }
+
/*
* Select destination tablespace (same as original unless user
* requested a change)
@@ -8576,6 +8692,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;
}
/*
@@ -13079,6 +13201,9 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation,
if (defList == NIL && operation != AT_ReplaceRelOptions)
return; /* nothing to do */
+ if (gtt_oncommit_option(defList) != ONCOMMIT_NOOP)
+ elog(ERROR, "table cannot add or modify on commit parameter by ALTER TABLE command.");
+
pgclass = table_open(RelationRelationId, RowExclusiveLock);
/* Fetch heap tuple */
@@ -13277,6 +13402,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 temporary table");
+
/* Check first if relation can be moved to new tablespace */
if (!CheckRelationTableSpaceMove(rel, newTableSpace))
{
@@ -13581,7 +13709,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,
@@ -14989,6 +15117,7 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
switch (rel->rd_rel->relpersistence)
{
case RELPERSISTENCE_TEMP:
+ case RELPERSISTENCE_GLOBAL_TEMP:
ereport(ERROR,
(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -17665,3 +17794,40 @@ ATExecAlterCollationRefreshVersion(Relation rel, List *coll)
index_update_collation_versions(rel->rd_id, get_collation_oid(coll, false));
CacheInvalidateRelcache(rel);
}
+
+/*
+ * Parse the on commit clause for the temporary table
+ */
+static OnCommitAction
+gtt_oncommit_option(List *options)
+{
+ ListCell *listptr;
+ OnCommitAction action = ONCOMMIT_NOOP;
+
+ foreach(listptr, options)
+ {
+ DefElem *def = (DefElem *) lfirst(listptr);
+
+ if (strcmp(def->defname, "on_commit_delete_rows") == 0)
+ {
+ bool res = false;
+ char *sval = defGetString(def);
+
+ /* It has to be a Boolean value */
+ if (!parse_bool(sval, &res))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"on_commit_delete_rows\" requires a Boolean value")));
+
+ if (res)
+ action = ONCOMMIT_DELETE_ROWS;
+ else
+ action = ONCOMMIT_PRESERVE_ROWS;
+
+ break;
+ }
+ }
+
+ return action;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 462f9a0..8c14f72 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"
@@ -1233,6 +1234,22 @@ vac_update_relstats(Relation relation,
HeapTuple ctup;
Form_pg_class pgcform;
bool dirty;
+ bool is_gtt = RELATION_IS_GLOBAL_TEMP(relation);
+
+ /* For global temporary table */
+ if (is_gtt)
+ {
+ /* Store relation statistics and transaction information to the localhash */
+ up_gtt_relstats(RelationGetRelid(relation),
+ num_pages, num_tuples,
+ num_all_visible_pages,
+ frozenxid, minmulti);
+
+ /* Update relation statistics to local relcache */
+ relation->rd_rel->relpages = (int32) num_pages;
+ relation->rd_rel->reltuples = (float4) num_tuples;
+ relation->rd_rel->relallvisible = (int32) num_all_visible_pages;
+ }
rd = table_open(RelationRelationId, RowExclusiveLock);
@@ -1246,17 +1263,23 @@ vac_update_relstats(Relation relation,
/* Apply statistical updates, if any, to copied tuple */
dirty = false;
- if (pgcform->relpages != (int32) num_pages)
+
+ if (!is_gtt &&
+ pgcform->relpages != (int32) num_pages)
{
pgcform->relpages = (int32) num_pages;
dirty = true;
}
- if (pgcform->reltuples != (float4) num_tuples)
+
+ if (!is_gtt &&
+ pgcform->reltuples != (float4) num_tuples)
{
pgcform->reltuples = (float4) num_tuples;
dirty = true;
}
- if (pgcform->relallvisible != (int32) num_all_visible_pages)
+
+ if (!is_gtt &&
+ pgcform->relallvisible != (int32) num_all_visible_pages)
{
pgcform->relallvisible = (int32) num_all_visible_pages;
dirty = true;
@@ -1301,7 +1324,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(),
@@ -1312,7 +1336,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)))
@@ -1421,6 +1446,13 @@ vac_update_datfrozenxid(void)
}
/*
+ * The relfrozenxid for a global temporary talble is stored in localhash,
+ * not pg_class, See list_all_session_gtt_frozenxids()
+ */
+ if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
+ /*
* Some table AMs might not need per-relation xid / multixid horizons.
* It therefore seems reasonable to allow relfrozenxid and relminmxid
* to not be set (i.e. set to their respective Invalid*Id)
@@ -1477,6 +1509,43 @@ vac_update_datfrozenxid(void)
Assert(TransactionIdIsNormal(newFrozenXid));
Assert(MultiXactIdIsValid(newMinMulti));
+ /* If enable global temporary table */
+ if (max_active_gtt > 0)
+ {
+ TransactionId safe_age;
+ /* */
+ TransactionId oldest_gtt_frozenxid =
+ list_all_backend_gtt_frozenxids(0, NULL, NULL, NULL);
+
+ if (TransactionIdIsNormal(oldest_gtt_frozenxid))
+ {
+ safe_age = oldest_gtt_frozenxid + vacuum_gtt_defer_check_age;
+ if (safe_age < FirstNormalTransactionId)
+ safe_age += FirstNormalTransactionId;
+
+ /*
+ * We tolerate that the minimum age of gtt is less than
+ * the minimum age of conventional tables, otherwise it will
+ * throw warning message.
+ */
+ if (TransactionIdIsNormal(safe_age) &&
+ TransactionIdPrecedes(safe_age, newFrozenXid))
+ {
+ ereport(WARNING,
+ (errmsg("global temp table oldest relfrozenxid %u is the oldest in the entire db",
+ oldest_gtt_frozenxid),
+ errdetail("The oldest relfrozenxid in pg_class is %u", newFrozenXid),
+ errhint("If they differ greatly, please consider cleaning up the data in global temp table.")));
+ }
+
+ /*
+ * We need to ensure that the clog required by gtt is not cleand.
+ */
+ if (TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+ newFrozenXid = oldest_gtt_frozenxid;
+ }
+ }
+
/* Now fetch the pg_database tuple we need to update. */
relation = table_open(DatabaseRelationId, RowExclusiveLock);
@@ -1829,6 +1898,19 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
}
/*
+ * Skip those global temporary table that are not initialized in
+ * current backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(onerel) &&
+ !gtt_storage_attached(RelationGetRelid(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
* contents are probably not up-to-date on disk. (We don't throw a
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index f2642db..e088101 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -530,6 +530,12 @@ DefineView(ViewStmt *stmt, const char *queryString,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("views cannot be unlogged because they do not have storage")));
+ /* Global temporary table are not sensible. */
+ if (stmt->view->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("views cannot be global temp because they do not have storage")));
+
/*
* If the user didn't explicitly ask for a temporary view, check whether
* we need one implicitly. We allow TEMP to be inserted automatically as
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index f4dd47a..ac0a4ee 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -787,6 +787,10 @@ ExecCheckXactReadOnly(PlannedStmt *plannedstmt)
if (isTempNamespace(get_rel_namespace(rte->relid)))
continue;
+ /* This is one kind of temp table */
+ if (get_rel_persistence(rte->relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ continue;
+
PreventCommandIfReadOnly(CreateCommandName((Node *) plannedstmt));
}
diff --git a/src/backend/executor/execPartition.c b/src/backend/executor/execPartition.c
index 746cd1e..f178b7e 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"
@@ -605,6 +606,9 @@ ExecInitPartitionInfo(ModifyTableState *mtstate, EState *estate,
(node != NULL &&
node->onConflictAction != ONCONFLICT_NONE));
+ /* Init storage for partitioned global temporary table in current backend */
+ 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 5d90337..de0b912 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"
@@ -2429,6 +2430,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
ExecOpenIndices(resultRelInfo,
node->onConflictAction != ONCONFLICT_NONE);
+ /* Init storage for global temporary table in current backend */
+ 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 026a4b0..e3c3c85 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -48,7 +48,7 @@
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
-
+#include "utils/rel.h"
/* results of subquery_is_pushdown_safe */
typedef struct pushdown_safety_info
@@ -623,7 +623,7 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
* the rest of the necessary infrastructure right now anyway. So
* for now, bail out if we see a temporary table.
*/
- if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(get_rel_persistence(rte->relid)))
return;
/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 4e6497f..d78ee73 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6430,7 +6430,7 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
* Furthermore, any index predicate or index expressions must be parallel
* safe.
*/
- if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+ if (RELATION_IS_TEMP(heap) ||
!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 177e6e3..8155a36 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/heap.h"
#include "catalog/index.h"
+#include "catalog/storage_gtt.h"
#include "catalog/pg_am.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_statistic_ext.h"
@@ -230,6 +231,14 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
continue;
}
+ /* Ignore empty index for global temporary table in current backend */
+ if (RELATION_IS_GLOBAL_TEMP(indexRelation) &&
+ !gtt_storage_attached(RelationGetRelid(indexRelation)))
+ {
+ index_close(indexRelation, NoLock);
+ continue;
+ }
+
/*
* If the index is valid, but cannot yet be used, ignore it; but
* mark the plan we are generating as transient. See
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 6548389..6ee3ed2 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2805,6 +2805,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 b2f447b..5509257 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3361,17 +3361,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; }
@@ -11452,19 +11446,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 e490043..c8df168 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);
/*
@@ -3609,3 +3610,53 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
isQueryUsingTempRelation_walker,
context);
}
+
+/*
+ * Like function isQueryUsingTempRelation_walker
+ * return true if any relation underlying
+ * the query is a global temporary table.
+ */
+static bool
+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 temporary 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 b31f3af..4da8fc2 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -457,6 +457,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
seqstmt->options = seqoptions;
/*
+ * If a sequence is bound to a global temporary table, then the sequence
+ * must been "global temporary"
+ */
+ if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ seqstmt->sequence->relpersistence = cxt->relation->relpersistence;
+
+ /*
* If a sequence data type was specified, add it to the options. Prepend
* to the list rather than append; in case a user supplied their own AS
* clause, the "redundant options" error will point to their occurrence,
@@ -3222,6 +3229,8 @@ transformAlterTableStmt(Oid relid, AlterTableStmt *stmt,
cxt.isforeign = false;
}
cxt.relation = stmt->relation;
+ /* Sets the table persistence to the context */
+ cxt.relation->relpersistence = RelationGetRelPersistence(rel);
cxt.rel = rel;
cxt.inhRelations = NIL;
cxt.isalter = true;
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 47e60ca..d3a01bf 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2111,6 +2111,14 @@ do_autovacuum(void)
}
continue;
}
+ else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /*
+ * Aotuvacuum cannot vacuum the private data stored in each backend
+ * that belongs to global temporary table, so skip them.
+ */
+ continue;
+ }
/* Fetch reloptions and the pgstat entry for this table */
relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2177,7 +2185,7 @@ do_autovacuum(void)
/*
* We cannot safely process other backends' temp tables, so skip 'em.
*/
- if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+ if (RelpersistenceTsTemp(classForm->relpersistence))
continue;
relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 561c212..003b65d 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"
@@ -2860,6 +2861,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
BlockNumber
RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
{
+ /*
+ * Returns 0 if this global temporary table is not initialized in current
+ * backend.
+ */
+ if (RELATION_IS_GLOBAL_TEMP(relation) &&
+ !gtt_storage_attached(RelationGetRelid(relation)))
+ {
+ return 0;
+ }
+
switch (relation->rd_rel->relkind)
{
case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index f9bbe97..df964e0 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -22,6 +22,7 @@
#include "access/subtrans.h"
#include "access/syncscan.h"
#include "access/twophase.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "miscadmin.h"
#include "pgstat.h"
@@ -149,6 +150,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
@@ -221,6 +223,8 @@ CreateSharedMemoryAndSemaphores(void)
SUBTRANSShmemInit();
MultiXactShmemInit();
InitBufferPool();
+ /* For global temporary table shared hashtable */
+ active_gtt_shared_hash_init();
/*
* Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index cf12eda..82475a6 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -65,6 +65,7 @@
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
+#include "utils/guc.h"
#define UINT32_ACCESS_ONCE(var) ((uint32)(*((volatile uint32 *)&(var))))
@@ -5044,3 +5045,78 @@ KnownAssignedXidsReset(void)
LWLockRelease(ProcArrayLock);
}
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temporary table.
+ */
+int
+list_all_backend_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+ ProcArrayStruct *arrayP = procArray;
+ TransactionId result = InvalidTransactionId;
+ int index;
+ uint8 flags = 0;
+ int i = 0;
+
+ /* return 0 if feature is disabled */
+ if (max_active_gtt <= 0)
+ return InvalidTransactionId;
+
+ if (max_size > 0)
+ {
+ Assert(pids);
+ Assert(xids);
+ Assert(n);
+ *n = 0;
+ }
+
+ /* Disable in standby node */
+ 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];
+ uint8 statusFlags = ProcGlobal->statusFlags[index];
+
+ if (statusFlags & flags)
+ continue;
+
+ /* Fetch all backend that is belonging to MyDatabaseId */
+ if (proc->databaseId == MyDatabaseId &&
+ TransactionIdIsNormal(proc->backend_gtt_frozenxid))
+ {
+ if (result == InvalidTransactionId)
+ result = proc->backend_gtt_frozenxid;
+ else if (TransactionIdPrecedes(proc->backend_gtt_frozenxid, result))
+ result = proc->backend_gtt_frozenxid;
+
+ /* save backend pid and backend level oldest relfrozenxid */
+ if (max_size > 0)
+ {
+ pids[i] = proc->pid;
+ xids[i] = proc->backend_gtt_frozenxid;
+ i++;
+ }
+ }
+ }
+ LWLockRelease(ProcArrayLock);
+
+ if (max_size > 0)
+ *n = i;
+
+ return result;
+}
+
diff --git a/src/backend/storage/lmgr/lwlock.c b/src/backend/storage/lmgr/lwlock.c
index 8cb6a6f..f7396c0 100644
--- a/src/backend/storage/lmgr/lwlock.c
+++ b/src/backend/storage/lmgr/lwlock.c
@@ -177,7 +177,9 @@ static const char *const BuiltinTrancheNames[] = {
/* LWTRANCHE_PARALLEL_APPEND: */
"ParallelAppend",
/* LWTRANCHE_PER_XACT_PREDICATE_LIST: */
- "PerXactPredicateList"
+ "PerXactPredicateList",
+ /* LWTRANCHE_GTT_CTL */
+ "GlobalTempTableControl"
};
StaticAssertDecl(lengthof(BuiltinTrancheNames) ==
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index c87ffc6..9534e46 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -392,6 +392,7 @@ InitProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
@@ -573,6 +574,7 @@ InitAuxiliaryProcess(void)
MyProc->databaseId = InvalidOid;
MyProc->roleId = InvalidOid;
MyProc->tempNamespaceId = InvalidOid;
+ MyProc->backend_gtt_frozenxid = InvalidTransactionId; /* init backend level gtt frozenxid */
MyProc->isBackgroundWorker = IsBackgroundWorker;
MyProc->delayChkpt = false;
MyProc->statusFlags = 0;
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index 64cdaa4..ef980e5 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -982,6 +982,13 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
Assert(backend != InvalidBackendId);
}
break;
+ case RELPERSISTENCE_GLOBAL_TEMP:
+ /*
+ * For global temporary table ,each backend has its own storage,
+ * also only sees its own storage. Use Backendid to identify them.
+ */
+ backend = BackendIdForTempRelations();
+ break;
default:
elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 47ca4dd..91d28f1 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -108,6 +108,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_statistic_ext.h"
+#include "catalog/storage_gtt.h"
#include "executor/nodeAgg.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -4889,12 +4890,26 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
}
else if (index->indpred == NIL)
{
- vardata->statsTuple =
- SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(index->indexoid),
- Int16GetDatum(pos + 1),
- BoolGetDatum(false));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(index->indexoid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ Int16GetDatum(pos + 1),
+ false);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ vardata->statsTuple =
+ SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(index->indexoid),
+ Int16GetDatum(pos + 1),
+ BoolGetDatum(false));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -5019,15 +5034,28 @@ examine_simple_variable(PlannerInfo *root, Var *var,
}
else if (rte->rtekind == RTE_RELATION)
{
- /*
- * Plain table or parent of an inheritance appendrel, so look up the
- * column in pg_statistic
- */
- vardata->statsTuple = SearchSysCache3(STATRELATTINH,
- ObjectIdGetDatum(rte->relid),
- Int16GetDatum(var->varattno),
- BoolGetDatum(rte->inh));
- vardata->freefunc = ReleaseSysCache;
+ char rel_persistence = get_rel_persistence(rte->relid);
+
+ if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+ var->varattno,
+ rte->inh);
+ vardata->freefunc = release_gtt_statistic_cache;
+ }
+ else
+ {
+ /*
+ * Plain table or parent of an inheritance appendrel, so look up the
+ * column in pg_statistic
+ */
+ vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(rte->relid),
+ Int16GetDatum(var->varattno),
+ BoolGetDatum(rte->inh));
+ vardata->freefunc = ReleaseSysCache;
+ }
if (HeapTupleIsValid(vardata->statsTuple))
{
@@ -6450,6 +6478,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;
@@ -6467,6 +6496,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ rte->inh);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6478,6 +6515,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;
@@ -6493,6 +6532,14 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple = get_gtt_att_statistic(relid,
+ colnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -7411,6 +7458,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))
@@ -7423,6 +7472,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
elog(ERROR,
"no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(rte->relid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple =
@@ -7435,6 +7493,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.
@@ -7454,6 +7514,15 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
!vardata.freefunc)
elog(ERROR, "no function provided to release variable stats with");
}
+ else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ vardata.statsTuple =
+ get_gtt_att_statistic(index->indexoid,
+ attnum,
+ false);
+ vardata.freefunc = release_gtt_statistic_cache;
+ }
else
{
vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 85c458b..6aa8e83 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -35,6 +35,7 @@
#include "catalog/pg_statistic.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "utils/array.h"
@@ -3086,6 +3087,19 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
if (stawidth > 0)
return stawidth;
}
+ if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ /* For global temporary table, get statistic data from localhash */
+ tp = get_gtt_att_statistic(relid, attnum, false);
+ if (!HeapTupleIsValid(tp))
+ return 0;
+
+ stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+ if (stawidth > 0)
+ return stawidth;
+ else
+ return 0;
+ }
tp = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 7ef510c..61fa8a3 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -66,6 +66,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"
@@ -1144,6 +1145,28 @@ 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;
+
+ /* For global temporary table, get relstat data from localhash */
+ get_gtt_relstats(RelationGetRelid(relation),
+ &relpages,
+ &reltuples,
+ &relallvisible,
+ NULL, NULL);
+
+ /* And put them to local relcache */
+ 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);
@@ -1198,6 +1221,8 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
case RELKIND_PARTITIONED_INDEX:
Assert(relation->rd_rel->relam != InvalidOid);
RelationInitIndexAccessInfo(relation);
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
break;
case RELKIND_RELATION:
case RELKIND_TOASTVALUE:
@@ -1325,7 +1350,22 @@ RelationInitPhysicalAddr(Relation relation)
heap_freetuple(phys_tuple);
}
- relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ if (RELATION_IS_GLOBAL_TEMP(relation))
+ {
+ Oid newrelnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ /*
+ * For global temporary table, get the latest relfilenode
+ * from localhash and put it in relcache.
+ */
+ if (OidIsValid(newrelnode) &&
+ newrelnode != relation->rd_rel->relfilenode)
+ relation->rd_node.relNode = newrelnode;
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
+ }
+ else
+ relation->rd_node.relNode = relation->rd_rel->relfilenode;
}
else
{
@@ -2267,6 +2307,9 @@ RelationReloadIndexInfo(Relation relation)
HeapTupleHeaderGetXmin(tuple->t_data));
ReleaseSysCache(tuple);
+
+ /* The state of the global temporary table's index may need to be set */
+ gtt_fix_index_backend_state(relation);
}
/* Okay, now it's valid again */
@@ -3493,6 +3536,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;
@@ -3600,28 +3647,38 @@ void
RelationSetNewRelfilenode(Relation relation, char persistence)
{
Oid newrelfilenode;
- Relation pg_class;
- HeapTuple tuple;
+ Relation pg_class = NULL;
+ HeapTuple tuple = NULL;
Form_pg_class classform;
MultiXactId minmulti = InvalidMultiXactId;
TransactionId freezeXid = InvalidTransactionId;
RelFileNode newrnode;
+ /*
+ * For global temporary table, storage information for the table is
+ * maintained locally, not in catalog.
+ */
+ bool update_catalog = !RELATION_IS_GLOBAL_TEMP(relation);
/* Allocate a new relfilenode */
newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
persistence);
- /*
- * Get a writable copy of the pg_class tuple for the given relation.
- */
- pg_class = table_open(RelationRelationId, RowExclusiveLock);
+ memset(&classform, 0, sizeof(classform));
- tuple = SearchSysCacheCopy1(RELOID,
- ObjectIdGetDatum(RelationGetRelid(relation)));
- if (!HeapTupleIsValid(tuple))
- elog(ERROR, "could not find tuple for relation %u",
- RelationGetRelid(relation));
- classform = (Form_pg_class) GETSTRUCT(tuple);
+ if (update_catalog)
+ {
+ /*
+ * Get a writable copy of the pg_class tuple for the given relation.
+ */
+ pg_class = table_open(RelationRelationId, RowExclusiveLock);
+
+ tuple = SearchSysCacheCopy1(RELOID,
+ ObjectIdGetDatum(RelationGetRelid(relation)));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u",
+ RelationGetRelid(relation));
+ classform = (Form_pg_class) GETSTRUCT(tuple);
+ }
/*
* Schedule unlinking of the old storage at transaction commit.
@@ -3647,7 +3704,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;
@@ -3667,6 +3724,18 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
break;
}
+ /* For global temporary table */
+ if (!update_catalog)
+ {
+ Oid relnode = gtt_fetch_current_relfilenode(RelationGetRelid(relation));
+
+ Assert(RELATION_IS_GLOBAL_TEMP(relation));
+ Assert(!RelationIsMapped(relation));
+
+ /* Make cache invalid and set new relnode to local cache. */
+ CacheInvalidateRelcache(relation);
+ relation->rd_node.relNode = relnode;
+ }
/*
* If we're dealing with a mapped index, pg_class.relfilenode doesn't
* change; instead we have to send the update to the relation mapper.
@@ -3676,7 +3745,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
* possibly-inaccurate values of relpages etc, but those will be fixed up
* later.
*/
- if (RelationIsMapped(relation))
+ else if (RelationIsMapped(relation))
{
/* This case is only supported for indexes */
Assert(relation->rd_rel->relkind == RELKIND_INDEX);
@@ -3722,9 +3791,12 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
}
- heap_freetuple(tuple);
+ if (update_catalog)
+ {
+ heap_freetuple(tuple);
- table_close(pg_class, RowExclusiveLock);
+ table_close(pg_class, RowExclusiveLock);
+ }
/*
* Make the pg_class row change or relation map change visible. This will
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index eafdb11..50682e6 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -40,6 +40,7 @@
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
#include "commands/async.h"
#include "commands/prepare.h"
#include "commands/trigger.h"
@@ -146,6 +147,18 @@ char *GUC_check_errmsg_string;
char *GUC_check_errdetail_string;
char *GUC_check_errhint_string;
+/*
+ * num = 0 means disable global temporary table feature.
+ * table schema are still saved in catalog.
+ *
+ * num > 0 means allows the database to manage multiple active tables at the same time.
+ */
+#define MIN_NUM_ACTIVE_GTT 0
+#define DEFAULT_NUM_ACTIVE_GTT 1000
+#define MAX_NUM_ACTIVE_GTT 1000000
+
+int max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -2058,6 +2071,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."),
@@ -2587,6 +2609,16 @@ static struct config_int ConfigureNamesInt[] =
NULL, NULL, NULL
},
+ {
+ {"vacuum_gtt_defer_check_age", PGC_USERSET, CLIENT_CONN_STATEMENT,
+ gettext_noop("The defer check age of GTT, used to check expired data after vacuum."),
+ NULL
+ },
+ &vacuum_gtt_defer_check_age,
+ 10000, 0, 1000000,
+ NULL, NULL, NULL
+ },
+
/*
* See also CheckRequiredParameterValues() if this parameter changes
*/
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 39da742..9f2d7e8 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -2434,6 +2434,10 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
dopt->no_unlogged_table_data)
return;
+ /* Don't dump data in global temporary table/sequence */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ return;
+
/* Check that the data is not explicitly excluded */
if (simple_oid_list_member(&tabledata_exclude_oids,
tbinfo->dobj.catId.oid))
@@ -15723,6 +15727,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
char *ftoptions = NULL;
char *srvname = NULL;
char *foreign = "";
+ char *table_type = NULL;
switch (tbinfo->relkind)
{
@@ -15776,9 +15781,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);
@@ -16143,13 +16154,22 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
}
/*
+ * Transaction information for the global temporary table is not stored
+ * in the pg_class.
+ */
+ if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+ {
+ Assert(tbinfo->frozenxid == 0);
+ Assert(tbinfo->minmxid == 0);
+ }
+ /*
* In binary_upgrade mode, arrange to restore the old relfrozenxid and
* relminmxid of all vacuumable relations. (While vacuum.c processes
* TOAST tables semi-independently, here we see them only as children
* of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
* child toast table is handled below.)
*/
- if (dopt->binary_upgrade &&
+ else if (dopt->binary_upgrade &&
(tbinfo->relkind == RELKIND_RELATION ||
tbinfo->relkind == RELKIND_MATVIEW))
{
@@ -17145,6 +17165,7 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
PQExpBuffer query = createPQExpBuffer();
PQExpBuffer delqry = createPQExpBuffer();
char *qseqname;
+ bool global_temp_seq = false;
qseqname = pg_strdup(fmtId(tbinfo->dobj.name));
@@ -17154,9 +17175,12 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
"SELECT format_type(seqtypid, NULL), "
"seqstart, seqincrement, "
"seqmax, seqmin, "
- "seqcache, seqcycle "
- "FROM pg_catalog.pg_sequence "
- "WHERE seqrelid = '%u'::oid",
+ "seqcache, seqcycle, "
+ "c.relpersistence "
+ "FROM pg_catalog.pg_sequence s, "
+ "pg_catalog.pg_class c "
+ "WHERE seqrelid = '%u'::oid "
+ "and s.seqrelid = c.oid",
tbinfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 80400)
@@ -17201,6 +17225,9 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
cache = PQgetvalue(res, 0, 5);
cycled = (strcmp(PQgetvalue(res, 0, 6), "t") == 0);
+ if (fout->remoteVersion >= 140000)
+ global_temp_seq = (strcmp(PQgetvalue(res, 0, 7), "g") == 0);
+
/* Calculate default limits for a sequence of this type */
is_ascending = (incby[0] != '-');
if (strcmp(seqtype, "smallint") == 0)
@@ -17278,9 +17305,13 @@ dumpSequence(Archive *fout, TableInfo *tbinfo)
}
else
{
- appendPQExpBuffer(query,
- "CREATE SEQUENCE %s\n",
- fmtQualifiedDumpable(tbinfo));
+ appendPQExpBuffer(query, "CREATE ");
+
+ if (global_temp_seq)
+ appendPQExpBuffer(query, "GLOBAL TEMP ");
+
+ appendPQExpBuffer(query, "SEQUENCE %s\n",
+ fmtQualifiedDumpable(tbinfo));
if (strcmp(seqtype, "bigint") != 0)
appendPQExpBuffer(query, " AS %s\n", seqtype);
diff --git a/src/bin/pg_upgrade/check.c b/src/bin/pg_upgrade/check.c
index 43fc297..ff33906 100644
--- a/src/bin/pg_upgrade/check.c
+++ b/src/bin/pg_upgrade/check.c
@@ -86,7 +86,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();
@@ -166,7 +166,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 5d9a26c..2de11d5 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);
@@ -447,8 +449,17 @@ get_rel_infos(ClusterInfo *cluster, DbInfo *dbinfo)
" FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n "
" ON c.relnamespace = n.oid "
" WHERE relkind IN (" CppAsString2(RELKIND_RELATION) ", "
- CppAsString2(RELKIND_MATVIEW) ") AND "
+ CppAsString2(RELKIND_MATVIEW) ") AND ");
+
+ if (skip_gtt)
+ {
+ /* exclude global temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND ");
+ }
+
/* exclude possible orphaned temp tables */
+ snprintf(query + strlen(query), sizeof(query) - strlen(query),
" ((n.nspname !~ '^pg_temp_' AND "
" n.nspname !~ '^pg_toast_temp_' AND "
" n.nspname NOT IN ('pg_catalog', 'information_schema', "
diff --git a/src/bin/pg_upgrade/pg_upgrade.c b/src/bin/pg_upgrade/pg_upgrade.c
index e23b8ca..729a9c6 100644
--- a/src/bin/pg_upgrade/pg_upgrade.c
+++ b/src/bin/pg_upgrade/pg_upgrade.c
@@ -407,7 +407,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);
}
/*
@@ -638,7 +638,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relfrozenxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
@@ -649,7 +652,10 @@ set_frozenxids(bool minmxid_only)
"UPDATE pg_catalog.pg_class "
"SET relminmxid = '%u' "
/* only heap, materialized view, and TOAST are vacuumed */
- "WHERE relkind IN ("
+ "WHERE "
+ /* exclude global temp tables */
+ " relpersistence != " CppAsString2(RELPERSISTENCE_GLOBAL_TEMP) " AND "
+ "relkind IN ("
CppAsString2(RELKIND_RELATION) ", "
CppAsString2(RELKIND_MATVIEW) ", "
CppAsString2(RELKIND_TOASTVALUE) ")",
diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index 919a784..486d01f 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -388,7 +388,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 20af5a9..d0a581e 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3758,7 +3758,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 17f7265..fa1b5e6 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1049,6 +1049,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},
@@ -2455,6 +2457,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" */
@@ -2663,6 +2668,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/heap.h b/src/include/catalog/heap.h
index 6ce480b..a0ccfb3 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -59,7 +59,8 @@ extern Relation heap_create(const char *relname,
bool mapped_relation,
bool allow_system_table_mods,
TransactionId *relfrozenxid,
- MultiXactId *relminmxid);
+ MultiXactId *relminmxid,
+ bool skip_create_storage);
extern Oid heap_create_with_catalog(const char *relname,
Oid relnamespace,
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index eca306c..dbd80c5 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -175,6 +175,7 @@ DECLARE_INDEX(pg_class_tblspc_relfilenode_index, 3455, on pg_class using btree(r
#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 f817406..674acd0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5624,6 +5624,40 @@
proparallel => 'r', prorettype => 'float8', proargtypes => 'oid',
prosrc => 'pg_stat_get_xact_function_self_time' },
+# For global temporary table
+{ oid => '4572',
+ 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 => '4573',
+ descr => 'List local relstats for global temporary table',
+ proname => 'pg_get_gtt_relstats', provolatile => 'v', proparallel => 'u',
+ prorettype => 'record', proretset => 't', prorows => '10', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int4,float4,int4,xid,xid}',
+ proargmodes => '{i,o,o,o,o,o,o}',
+ proargnames => '{relid,relfilenode,relpages,reltuples,relallvisible,relfrozenxid,relminmxid}',
+ prosrc => 'pg_get_gtt_relstats' },
+{ oid => '4574',
+ 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 => '4575',
+ 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 0ab32b4..92e9f8b 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -22,7 +22,7 @@
/* GUC variables */
extern int wal_skip_threshold;
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
extern void RelationDropStorage(Relation rel);
extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
extern void RelationPreTruncate(Relation rel);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..d48162c
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,46 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ * prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "nodes/execnodes.h"
+#include "utils/relcache.h"
+
+extern int vacuum_gtt_defer_check_age;
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid, RelFileNode relfilenode, bool isCommit);
+extern bool is_other_backend_use_gtt(Oid relid);
+extern bool gtt_storage_attached(Oid relid);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+ TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Oid relid,
+ 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 force_enable_gtt_index(Relation index);
+extern void gtt_fix_index_backend_state(Relation index);
+extern void init_gtt_storage(CmdType operation, ResultRelInfo *resultRelInfo);
+extern Oid gtt_fetch_current_relfilenode(Oid relid);
+extern void gtt_switch_rel_relfilenode(Oid rel1, Oid relfilenode1, Oid rel2, Oid relfilenode2, bool footprint);
+
+#endif /* STORAGE_H */
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 40544dd..7b66d80 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 35e6407..3230245 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 359b749..e724f7e 100644
--- a/src/include/storage/bufpage.h
+++ b/src/include/storage/bufpage.h
@@ -399,6 +399,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 cbf2510..5ef01bc 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -218,6 +218,7 @@ typedef enum BuiltinTrancheIds
LWTRANCHE_SHARED_TIDBITMAP,
LWTRANCHE_PARALLEL_APPEND,
LWTRANCHE_PER_XACT_PREDICATE_LIST,
+ LWTRANCHE_GTT_CTL,
LWTRANCHE_FIRST_USER_DEFINED
} BuiltinTrancheIds;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 683ab64..faaa83b 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -157,6 +157,8 @@ struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
+ TransactionId backend_gtt_frozenxid; /* backend 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 b01fa52..8efffa5 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -94,4 +94,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
TransactionId *catalog_xmin);
+extern int list_all_backend_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 5004ee4..e03f02d 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -283,6 +283,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 10b6398..4719c2e 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
@@ -306,6 +306,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
@@ -571,11 +572,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.
*
@@ -583,6 +586,7 @@ typedef struct ViewOptions
*/
#define RELATION_IS_LOCAL(relation) \
((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP || \
(relation)->rd_createSubid != InvalidSubTransactionId)
/*
@@ -595,6 +599,30 @@ typedef struct ViewOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp)
+/*
+ * RELATION_IS_TEMP_ON_CURRENT_SESSION
+ * Test a rel is either local temp relation of this session
+ * or global temp relation.
+ */
+#define RELATION_IS_TEMP_ON_CURRENT_SESSION(relation) \
+ ((relation)->rd_islocaltemp || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RELATION_IS_TEMP
+ * Test a rel is local temp relation or global temporary relation.
+ */
+#define RELATION_IS_TEMP(relation) \
+ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP || \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/*
+ * RelpersistenceTsTemp
+ * Test a relpersistence is local temp relation or global temporary relation.
+ */
+#define RelpersistenceTsTemp(relpersistence) \
+ (relpersistence == RELPERSISTENCE_TEMP || \
+ relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
/*
* RelationIsScannable
@@ -638,6 +666,19 @@ typedef struct ViewOptions
RelationNeedsWAL(relation) && \
!IsCatalogRelation(relation))
+/* For global temporary table */
+#define RELATION_IS_GLOBAL_TEMP(relation) ((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+/* Get on commit clause value only for global temporary table */
+#define RELATION_GTT_ON_COMMIT_DELETE(relation) \
+ ((relation)->rd_options && \
+ ((relation)->rd_rel->relkind == RELKIND_RELATION || (relation)->rd_rel->relkind == RELKIND_PARTITIONED_TABLE) && \
+ (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+ ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+/* Get relpersistence for relation */
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
/* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..ca2d135
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,14 @@
+reset search_path;
+select pg_sleep(5);
+ pg_sleep
+----------
+
+(1 row)
+
+drop schema gtt cascade;
+NOTICE: drop cascades to 5 other objects
+DETAIL: drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
+drop cascades to table gtt.gtt_with_seq
diff --git a/src/test/regress/expected/gtt_function.out b/src/test/regress/expected/gtt_function.out
new file mode 100644
index 0000000..0ac9e22
--- /dev/null
+++ b/src/test/regress/expected/gtt_function.out
@@ -0,0 +1,381 @@
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+set search_path=gtt_function,sys;
+create global temp table gtt1(a int primary key, b text);
+create global temp table gtt_test_rename(a int primary key, b text);
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+ n
+---
+ 9
+(1 row)
+
+commit;
+-- 0 row
+select * from gtt6;
+ n
+---
+(0 rows)
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR: ON COMMIT can only be used on temporary tables
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+ERROR: not support alter table set tablespace on global temporary table
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR: The parameter on_commit_delete_rows is exclusive to the global temporary table, which cannot be specified by a regular table
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+ERROR: views cannot be global temp because they do not have storage
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+ERROR: table cannot add or modify on commit parameter by ALTER TABLE command.
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+ count
+-------
+ 2
+(1 row)
+
+commit;
+select count(*) from p_table01;
+ count
+-------
+ 0
+(1 row)
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR: The parent table must be global temporary table
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+ relname | relkind | relpersistence | reloptions
+---------------------------------+---------+----------------+-------------------------------
+ p_table01 | p | g | {on_commit_delete_rows=true}
+ p_table01_2017 | r | g | {on_commit_delete_rows=true}
+ p_table01_2018 | r | g | {on_commit_delete_rows=true}
+ p_table01_id_seq | S | g |
+ p_table02 | p | g | {on_commit_delete_rows=false}
+ p_table02_2017 | r | g | {on_commit_delete_rows=false}
+ p_table02_2018 | r | g | {on_commit_delete_rows=false}
+ p_table02_id_seq | S | g |
+ tbl_inherits_parent | r | p |
+ tbl_inherits_parent_global_temp | r | g | {on_commit_delete_rows=true}
+ tbl_inherits_partition | r | g | {on_commit_delete_rows=true}
+(11 rows)
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR: global temporary table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR: could not create global temporary table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+ERROR: constraints on permanent tables may reference only permanent tables
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+--ERROR
+insert into orders values(1,1,1);
+ERROR: insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
+DETAIL: Key (product_no)=(1) is not present in table "products".
+--ok
+insert into products values(1,'test',1.0);
+begin;
+insert into orders values(1,1,1);
+commit;
+select count(*) from products;
+ count
+-------
+ 1
+(1 row)
+
+select count(*) from orders;
+ count
+-------
+ 0
+(1 row)
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 2 | 1
+ 3 | 2
+(2 rows)
+
+truncate gtt_seq;
+select * from gtt_seq order by id;
+ id | a
+----+---
+(0 rows)
+
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+ id | a
+----+---
+ 4 | 3
+(1 row)
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR: materialized views must not use global temporary tables or views
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+ a | b
+---+---
+ 1 | 1
+(1 row)
+
+commit;
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+ 1 | 1
+ 2 | 3
+(2 rows)
+
+commit;
+select * from gtt_s_1 order by c1;
+ c1 | c2
+----+----
+(0 rows)
+
+select * from gtt_s_2 order by c1;
+ c1 | c2
+----+----
+ 1 | 2
+ 2 | 4
+(2 rows)
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 1)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 1)
+(4 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: (a = 200000)
+ -> Bitmap Index Scan on idx_gt1_1
+ Index Cond: (a = 200000)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 300)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 300)
+(4 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+--------------------------------------
+ Bitmap Heap Scan on gt1
+ Recheck Cond: ((a * 10) = 3)
+ -> Bitmap Index Scan on idx_gt1_3
+ Index Cond: ((a * 10) = 3)
+(4 rows)
+
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 1)
+(2 rows)
+
+explain (costs off) select * from gt1 where a=200000;
+ QUERY PLAN
+----------------------------------------
+ Index Only Scan using idx_gt1_1 on gt1
+ Index Cond: (a = 200000)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=300;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 300)
+(2 rows)
+
+explain (costs off) select * from gt1 where a*10=3;
+ QUERY PLAN
+-----------------------------------
+ Index Scan using idx_gt1_3 on gt1
+ Index Cond: ((a * 10) = 3)
+(2 rows)
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+reset search_path;
+drop schema gtt_function cascade;
+NOTICE: drop cascades to 33 other objects
+DETAIL: drop cascades to table gtt_function.gtt1
+drop cascades to table gtt_function.gtt_test_new
+drop cascades to table gtt_function.gtt2
+drop cascades to table gtt_function.gtt3
+drop cascades to table gtt_function.tmp_t0
+drop cascades to table gtt_function.tbl_inherits_parent
+drop cascades to table gtt_function.tbl_inherits_parent_global_temp
+drop cascades to table gtt_function.products
+drop cascades to table gtt_function.gtt6
+drop cascades to table gtt_function.foo
+drop cascades to table gtt_function.measurement
+drop cascades to table gtt_function.p_table01
+drop cascades to table gtt_function.p_table02
+drop cascades to table gtt_function.tbl_inherits_partition
+drop cascades to table gtt_function.gtt5
+drop cascades to table gtt_function.orders
+drop cascades to table gtt_function.mytable
+drop cascades to table gtt_function.gtt_seq
+drop cascades to table gtt_function.gt
+drop cascades to sequence gtt_function.seq_1
+drop cascades to table gtt_function.gtt_s_1
+drop cascades to table gtt_function.gtt_s_2
+drop cascades to table gtt_function.gt1
+drop cascades to table gtt_function.gtt_test1
+drop cascades to table gtt_function.gtt_test2
+drop cascades to table gtt_function.gtt_test3
+drop cascades to table gtt_function.gtt_test4
+drop cascades to table gtt_function.gtt_test5
+drop cascades to table gtt_function.gtt_test6
+drop cascades to table gtt_function.gtt_test7
+drop cascades to table gtt_function.gtt_test8
+drop cascades to table gtt_function.gtt_test9
+drop cascades to table gtt_function.gtt_test10
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..0fccf6b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,343 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a | b
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+rollback;
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+(1 row)
+
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+ a | b
+---+---
+ 1 |
+ 2 |
+(2 rows)
+
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+ a | b
+---+---
+ 4 |
+(1 row)
+
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+ a | b
+---+---
+ 3 |
+(1 row)
+
+insert into gtt3 values(5);
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+commit;
+select * from gtt3;
+ a | b
+---+---
+ 3 |
+ 5 |
+(2 rows)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+ QUERY PLAN
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+ Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+ relname | pg_relation_size | pg_relation_size | pg_table_size | pg_indexes_size | pg_total_relation_size
+--------------+------------------+------------------+---------------+-----------------+------------------------
+ gtt_t_kenyon | 450560 | 8192 | 499712 | 114688 | 614400
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+ relname
+--------------
+ gtt_t_kenyon
+(1 row)
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+ relname | pg_relation_size | pg_table_size | pg_total_relation_size
+--------------------+------------------+---------------+------------------------
+ idx_gtt_t_kenyon_1 | 65536 | 65536 | 65536
+ idx_gtt_t_kenyon_2 | 49152 | 49152 | 49152
+(2 rows)
+
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2000
+(1 row)
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+ count
+-------
+ 2006
+(1 row)
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+ c1 | c2
+----+----
+ 1 | 1
+(1 row)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_prepare.out b/src/test/regress/expected/gtt_prepare.out
new file mode 100644
index 0000000..8c0c376
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,10 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+create global temp table gtt3(a int primary key, b text);
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/expected/gtt_stats.out b/src/test/regress/expected/gtt_stats.out
new file mode 100644
index 0000000..4420fdb
--- /dev/null
+++ b/src/test/regress/expected/gtt_stats.out
@@ -0,0 +1,80 @@
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+set search_path=gtt_stats,sys;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 0
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 0
+(1 row)
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+insert into gtt values(generate_series(1,10000),'test');
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+ count
+-------
+ 1
+(1 row)
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+ count
+-------
+ 2
+(1 row)
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 0 | 0 | 0
+ gtt_stats | gtt_pkey | 1 | 0 | 0
+(2 rows)
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+reindex table gtt;
+reindex index gtt_pkey;
+analyze gtt;
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+ schemaname | tablename | relpages | reltuples | relallvisible
+------------+-----------+----------+-----------+---------------
+ gtt_stats | gtt | 55 | 10000 | 0
+ gtt_stats | gtt_pkey | 30 | 10000 | 0
+(2 rows)
+
+select * from pg_gtt_stats order by tablename;
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------+-------------------+------------------------+----------------------
+ gtt_stats | gtt | a | f | 0 | 4 | -1 | | | {1,100,200,300,400,500,600,700,800,900,1000,1100,1200,1300,1400,1500,1600,1700,1800,1900,2000,2100,2200,2300,2400,2500,2600,2700,2800,2900,3000,3100,3200,3300,3400,3500,3600,3700,3800,3900,4000,4100,4200,4300,4400,4500,4600,4700,4800,4900,5000,5100,5200,5300,5400,5500,5600,5700,5800,5900,6000,6100,6200,6300,6400,6500,6600,6700,6800,6900,7000,7100,7200,7300,7400,7500,7600,7700,7800,7900,8000,8100,8200,8300,8400,8500,8600,8700,8800,8900,9000,9100,9200,9300,9400,9500,9600,9700,9800,9900,10000} | 1 | | |
+ gtt_stats | gtt | b | f | 0 | 5 | 1 | {test} | {1} | | 1 | | |
+(2 rows)
+
+reset search_path;
+drop schema gtt_stats cascade;
+NOTICE: drop cascades to table gtt_stats.gtt
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 6173473..864e094 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1359,6 +1359,94 @@ pg_group| SELECT pg_authid.rolname AS groname,
WHERE (pg_auth_members.roleid = pg_authid.oid)) AS grolist
FROM pg_authid
WHERE (NOT pg_authid.rolcanlogin);
+pg_gtt_attached_pids| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relid,
+ s.pid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_gtt_attached_pid(c.oid) s(relid, pid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'S'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_relstats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ s.relfilenode,
+ s.relpages,
+ s.reltuples,
+ s.relallvisible,
+ s.relfrozenxid,
+ s.relminmxid
+ FROM (pg_class c
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_relstats(c.oid) s(relfilenode, relpages, reltuples, relallvisible, relfrozenxid, relminmxid)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
+pg_gtt_stats| SELECT n.nspname AS schemaname,
+ c.relname AS tablename,
+ a.attname,
+ s.stainherit AS inherited,
+ s.stanullfrac AS null_frac,
+ s.stawidth AS avg_width,
+ s.stadistinct AS n_distinct,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stavalues1
+ WHEN (s.stakind2 = 1) THEN s.stavalues2
+ WHEN (s.stakind3 = 1) THEN s.stavalues3
+ WHEN (s.stakind4 = 1) THEN s.stavalues4
+ WHEN (s.stakind5 = 1) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_vals,
+ CASE
+ WHEN (s.stakind1 = 1) THEN s.stanumbers1
+ WHEN (s.stakind2 = 1) THEN s.stanumbers2
+ WHEN (s.stakind3 = 1) THEN s.stanumbers3
+ WHEN (s.stakind4 = 1) THEN s.stanumbers4
+ WHEN (s.stakind5 = 1) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_freqs,
+ CASE
+ WHEN (s.stakind1 = 2) THEN s.stavalues1
+ WHEN (s.stakind2 = 2) THEN s.stavalues2
+ WHEN (s.stakind3 = 2) THEN s.stavalues3
+ WHEN (s.stakind4 = 2) THEN s.stavalues4
+ WHEN (s.stakind5 = 2) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS histogram_bounds,
+ CASE
+ WHEN (s.stakind1 = 3) THEN s.stanumbers1[1]
+ WHEN (s.stakind2 = 3) THEN s.stanumbers2[1]
+ WHEN (s.stakind3 = 3) THEN s.stanumbers3[1]
+ WHEN (s.stakind4 = 3) THEN s.stanumbers4[1]
+ WHEN (s.stakind5 = 3) THEN s.stanumbers5[1]
+ ELSE NULL::real
+ END AS correlation,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stavalues1
+ WHEN (s.stakind2 = 4) THEN s.stavalues2
+ WHEN (s.stakind3 = 4) THEN s.stavalues3
+ WHEN (s.stakind4 = 4) THEN s.stavalues4
+ WHEN (s.stakind5 = 4) THEN s.stavalues5
+ ELSE NULL::text[]
+ END AS most_common_elems,
+ CASE
+ WHEN (s.stakind1 = 4) THEN s.stanumbers1
+ WHEN (s.stakind2 = 4) THEN s.stanumbers2
+ WHEN (s.stakind3 = 4) THEN s.stanumbers3
+ WHEN (s.stakind4 = 4) THEN s.stanumbers4
+ WHEN (s.stakind5 = 4) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS most_common_elem_freqs,
+ CASE
+ WHEN (s.stakind1 = 5) THEN s.stanumbers1
+ WHEN (s.stakind2 = 5) THEN s.stanumbers2
+ WHEN (s.stakind3 = 5) THEN s.stanumbers3
+ WHEN (s.stakind4 = 5) THEN s.stanumbers4
+ WHEN (s.stakind5 = 5) THEN s.stanumbers5
+ ELSE NULL::real[]
+ END AS elem_count_histogram
+ FROM ((pg_class c
+ JOIN pg_attribute a ON ((c.oid = a.attrelid)))
+ LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))),
+ LATERAL pg_get_gtt_statistics(c.oid, (a.attnum)::integer, ''::text) s(starelid, staattnum, stainherit, stanullfrac, stawidth, stadistinct, stakind1, stakind2, stakind3, stakind4, stakind5, staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5, stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5, stavalues1, stavalues2, stavalues3, stavalues4, stavalues5)
+ WHERE ((c.relpersistence = 'g'::"char") AND (c.relkind = ANY (ARRAY['r'::"char", 'p'::"char", 'i'::"char", 't'::"char"])) AND (NOT a.attisdropped) AND has_column_privilege(c.oid, a.attnum, 'select'::text) AND ((c.relrowsecurity = false) OR (NOT row_security_active(c.oid))));
pg_hba_file_rules| SELECT a.line_number,
a.type,
a.database,
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e0e1ef7..c8148f0 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -123,3 +123,10 @@ test: fast_default
# run stats by itself because its delay may be insufficient under heavy load
test: stats
+
+# global temp table test
+test: gtt_stats
+test: gtt_function
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_clean.sql b/src/test/regress/sql/gtt_clean.sql
new file mode 100644
index 0000000..2c8e586
--- /dev/null
+++ b/src/test/regress/sql/gtt_clean.sql
@@ -0,0 +1,8 @@
+
+
+reset search_path;
+
+select pg_sleep(5);
+
+drop schema gtt cascade;
+
diff --git a/src/test/regress/sql/gtt_function.sql b/src/test/regress/sql/gtt_function.sql
new file mode 100644
index 0000000..fd8b4d3
--- /dev/null
+++ b/src/test/regress/sql/gtt_function.sql
@@ -0,0 +1,253 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_function;
+
+set search_path=gtt_function,sys;
+
+create global temp table gtt1(a int primary key, b text);
+
+create global temp table gtt_test_rename(a int primary key, b text);
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text) on commit PRESERVE rows;
+
+create global temp table tmp_t0(c0 tsvector,c1 varchar(100));
+
+create table tbl_inherits_parent(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+);
+
+create global temp table tbl_inherits_parent_global_temp(
+a int not null,
+b varchar(32) not null default 'Got u',
+c int check (c > 0),
+d date not null
+)on commit delete rows;
+
+CREATE global temp TABLE products (
+ product_no integer PRIMARY KEY,
+ name text,
+ price numeric
+);
+
+create global temp table gtt6(n int) with (on_commit_delete_rows='true');
+
+begin;
+insert into gtt6 values (9);
+-- 1 row
+select * from gtt6;
+commit;
+-- 0 row
+select * from gtt6;
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+alter table gtt1 SET TABLESPACE pg_default;
+
+-- ERROR
+alter table gtt1 set ( on_commit_delete_rows='true');
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+create or replace global temp view gtt_v as select 5;
+
+create table foo();
+-- ERROR
+alter table foo set (on_commit_delete_rows='true');
+
+-- ok
+CREATE global temp TABLE measurement (
+ logdate date not null,
+ peaktemp int,
+ unitsales int
+) PARTITION BY RANGE (logdate);
+
+--ok
+CREATE global temp TABLE p_table01 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)on commit delete rows;
+
+CREATE global temp TABLE p_table01_2018
+PARTITION OF p_table01
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00') on commit delete rows;
+
+CREATE global temp TABLE p_table01_2017
+PARTITION OF p_table01
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00') on commit delete rows;
+
+begin;
+insert into p_table01 values(1,'2018-01-02 00:00:00','test1');
+insert into p_table01 values(1,'2018-01-02 00:00:00','test2');
+select count(*) from p_table01;
+commit;
+
+select count(*) from p_table01;
+
+--ok
+CREATE global temp TABLE p_table02 (
+id bigserial NOT NULL,
+cre_time timestamp without time zone,
+note varchar(30)
+) PARTITION BY RANGE (cre_time)
+WITH (
+OIDS = FALSE
+)
+on commit PRESERVE rows;
+
+CREATE global temp TABLE p_table02_2018
+PARTITION OF p_table02
+FOR VALUES FROM ('2018-01-01 00:00:00') TO ('2019-01-01 00:00:00');
+
+CREATE global temp TABLE p_table02_2017
+PARTITION OF p_table02
+FOR VALUES FROM ('2017-01-01 00:00:00') TO ('2018-01-01 00:00:00');
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- ok
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent_global_temp) on commit delete rows;
+
+select relname ,relkind, relpersistence, reloptions from pg_class where relname like 'p_table0%' or relname like 'tbl_inherits%' order by relname;
+
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+
+--ok
+alter table gtt_test_rename rename to gtt_test_new;
+
+-- ok
+ALTER TABLE gtt_test_new ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+);
+
+-- ok
+CREATE global temp TABLE orders (
+ order_id integer PRIMARY KEY,
+ product_no integer REFERENCES products (product_no),
+ quantity integer
+)on commit delete rows;
+
+--ERROR
+insert into orders values(1,1,1);
+
+--ok
+insert into products values(1,'test',1.0);
+
+begin;
+insert into orders values(1,1,1);
+commit;
+
+select count(*) from products;
+select count(*) from orders;
+
+-- ok
+CREATE GLOBAL TEMPORARY TABLE mytable (
+ id SERIAL PRIMARY KEY,
+ data text
+) on commit preserve rows;
+
+-- ok
+create global temp table gtt_seq(id int GENERATED ALWAYS AS IDENTITY (START WITH 2) primary key, a int) on commit PRESERVE rows;
+insert into gtt_seq (a) values(1);
+insert into gtt_seq (a) values(2);
+select * from gtt_seq order by id;
+truncate gtt_seq;
+select * from gtt_seq order by id;
+insert into gtt_seq (a) values(3);
+select * from gtt_seq order by id;
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ok
+create index idx_gtt1_1 on gtt1 using hash (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_2 on tmp_t0 using gist (c0);
+
+--ok
+create global temp table gt (a SERIAL,b int);
+begin;
+set transaction_read_only = true;
+insert into gt (b) values(1);
+select * from gt;
+commit;
+
+create sequence seq_1;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_1(c1 int PRIMARY KEY) ON COMMIT DELETE ROWS;
+CREATE GLOBAL TEMPORARY TABLE gtt_s_2(c1 int PRIMARY KEY) ON COMMIT PRESERVE ROWS;
+alter table gtt_s_1 add c2 int default nextval('seq_1');
+alter table gtt_s_2 add c2 int default nextval('seq_1');
+begin;
+insert into gtt_s_1 (c1)values(1);
+insert into gtt_s_2 (c1)values(1);
+insert into gtt_s_1 (c1)values(2);
+insert into gtt_s_2 (c1)values(2);
+select * from gtt_s_1 order by c1;
+commit;
+select * from gtt_s_1 order by c1;
+select * from gtt_s_2 order by c1;
+
+--ok
+create global temp table gt1(a int);
+insert into gt1 values(generate_series(1,100000));
+create index idx_gt1_1 on gt1 (a);
+create index idx_gt1_2 on gt1((a + 1));
+create index idx_gt1_3 on gt1((a*10),(a+a),(a-1));
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+analyze gt1;
+explain (costs off) select * from gt1 where a=1;
+explain (costs off) select * from gt1 where a=200000;
+explain (costs off) select * from gt1 where a*10=300;
+explain (costs off) select * from gt1 where a*10=3;
+
+--ok
+create global temp table gtt_test1(c1 int) with(on_commit_delete_rows='1');
+create global temp table gtt_test2(c1 int) with(on_commit_delete_rows='0');
+create global temp table gtt_test3(c1 int) with(on_commit_delete_rows='t');
+create global temp table gtt_test4(c1 int) with(on_commit_delete_rows='f');
+create global temp table gtt_test5(c1 int) with(on_commit_delete_rows='yes');
+create global temp table gtt_test6(c1 int) with(on_commit_delete_rows='no');
+create global temp table gtt_test7(c1 int) with(on_commit_delete_rows='y');
+create global temp table gtt_test8(c1 int) with(on_commit_delete_rows='n');
+
+--error
+create global temp table gtt_test9(c1 int) with(on_commit_delete_rows='tr');
+create global temp table gtt_test10(c1 int) with(on_commit_delete_rows='ye');
+
+reset search_path;
+
+drop schema gtt_function cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d05745e
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,44 @@
+
+
+set search_path=gtt,sys;
+
+select nextval('gtt_with_seq_c2_seq');
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..81a6039
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,154 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+rollback;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(1);
+select * from gtt3;
+begin;
+insert into gtt3 values(2);
+select * from gtt3;
+SAVEPOINT save1;
+truncate gtt3;
+insert into gtt3 values(3);
+select * from gtt3;
+SAVEPOINT save2;
+truncate gtt3;
+insert into gtt3 values(4);
+select * from gtt3;
+SAVEPOINT save3;
+rollback to savepoint save2;
+Select * from gtt3;
+insert into gtt3 values(5);
+select * from gtt3;
+commit;
+select * from gtt3;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid),pg_table_size(oid),pg_indexes_size(oid),pg_total_relation_size(oid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+select
+c.relname, pg_relation_size(c.oid),pg_table_size(c.oid),pg_total_relation_size(c.oid)
+from
+pg_class c
+where
+c.oid in
+(
+select
+i.indexrelid as indexrelid
+from
+pg_index i ,pg_class cc
+where cc.relname = 'gtt_t_kenyon' and cc.oid = i.indrelid
+)
+order by c.relname;
+
+select count(*) from gtt_t_kenyon;
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+begin;
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+vacuum full gtt_t_kenyon;
+select count(*) from gtt_t_kenyon;
+
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+commit;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+begin;
+truncate gtt_t_kenyon;
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+cluster gtt_t_kenyon using idx_gtt_t_kenyon_1;
+rollback;
+select count(*) from gtt_t_kenyon;
+
+insert into gtt_with_seq (c1) values(1);
+select * from gtt_with_seq;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_prepare.sql b/src/test/regress/sql/gtt_prepare.sql
new file mode 100644
index 0000000..dbe84d1
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,19 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt2(a int primary key, b text) on commit delete rows;
+
+create global temp table gtt3(a int primary key, b text);
+
+create global temp table gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+create index idx_gtt_t_kenyon_1 on gtt_t_kenyon(id);
+create index idx_gtt_t_kenyon_2 on gtt_t_kenyon(remark);
+
+CREATE GLOBAL TEMPORARY TABLE gtt_with_seq(c1 bigint, c2 bigserial) on commit PRESERVE rows;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_stats.sql b/src/test/regress/sql/gtt_stats.sql
new file mode 100644
index 0000000..d61b0ff
--- /dev/null
+++ b/src/test/regress/sql/gtt_stats.sql
@@ -0,0 +1,46 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_stats;
+
+set search_path=gtt_stats,sys;
+
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+create global temp table gtt_stats.gtt(a int primary key, b text) on commit PRESERVE rows;
+-- expect 0
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 0
+select count(*) from pg_list_gtt_relfrozenxids();
+
+insert into gtt values(generate_series(1,10000),'test');
+
+-- expect 1
+select count(*) from pg_gtt_attached_pids;
+
+-- expect 2
+select count(*) from pg_list_gtt_relfrozenxids();
+
+-- expect 2
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+-- expect 0
+select * from pg_gtt_stats order by tablename;
+
+reindex table gtt;
+
+reindex index gtt_pkey;
+
+analyze gtt;
+
+select schemaname, tablename, relpages, reltuples, relallvisible from pg_gtt_relstats where schemaname = 'gtt_stats' order by tablename;
+
+select * from pg_gtt_stats order by tablename;
+
+reset search_path;
+
+drop schema gtt_stats cascade;
+
Hi,
Trying to apply the last patch (global_temporary_table_v42-pg14.patch) to the code cloned from github, ending with this error:
usr/bin/ld: catalog/storage_gtt.o: in function `up_gtt_relstats':
storage_gtt.c:(.text+0x1276): undefined reference to `ReadNewTransactionId'
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:66: postgres] Error 1
make[2]: Leaving directory '/root/downloads/postgres/src/backend'
make[1]: *** [Makefile:42: all-backend-recurse] Error 2
make[1]: Leaving directory '/root/downloads/postgres/src'
make: *** [GNUmakefile:11: all-src-recurse] Error 2
Anyway, what's the next step to see this very important feature included in v14 ?
Best regards,
Alexandre