On 06.11.2019 16:24, 曾文旌(义从) wrote:
Dear Hackers


I attached the patch of GTT implementationI base on PG12.
The GTT design came from my first email.
Some limitations in patch will be eliminated in later versions.

Later, I will comment on Konstantin's patch and make some proposals for 
cooperation.
Looking forward to your feedback.

Thanks.

Zeng Wenjing


Thank you for this patch.
My first comments:

1.  I have ported you patch to the latest Postgres version (my patch is attached). 2. You patch is supporting only B-Tree index for GTT. All other indexes (hash, gin, gist, brin,...) are not currently supported.
3. I do not understand the reason for the following limitation:
"We allow to create index on global temp table only this session use it"

First of all it seems to significantly reduce usage of global temp tables.
Why do we need GTT at all? Mostly because we need to access temporary data in more than one backend. Otherwise we can just use normal table. If temp table is expected to be larger enough, so that we need to create index for it, then it is hard to believe that it will be needed only in one backend.

May be the assumption is that all indexes has to be created before GTT start to be used. But right now this check is not working correctly in any case - if you insert some data into the table, then
you can not create index any more:

postgres=# create global temp table gtt(x integer primary key, y integer);
CREATE TABLE
postgres=# insert into gtt values (generate_series(1,100000), generate_series(1,100000));
INSERT 0 100000
postgres=# create index on gtt(y);
ERROR:  can not create index when have one or more backend attached this global temp table

I wonder why do you need such restriction?


--
Konstantin Knizhnik
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company

diff --git a/contrib/Makefile b/contrib/Makefile
index 92184ed..4b1a596 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -48,7 +48,8 @@ SUBDIRS = \
 		tsm_system_rows \
 		tsm_system_time \
 		unaccent	\
-		vacuumlo
+		vacuumlo	\
+		pg_gtt
 
 ifeq ($(with_openssl),yes)
 SUBDIRS += sslinfo
diff --git a/contrib/pg_gtt/Makefile b/contrib/pg_gtt/Makefile
new file mode 100644
index 0000000..1d2ef64
--- /dev/null
+++ b/contrib/pg_gtt/Makefile
@@ -0,0 +1,22 @@
+# contrib/pg_gtt/Makefile
+
+MODULE_big = pg_gtt
+OBJS = pg_gtt.o
+
+EXTENSION = pg_gtt
+DATA = pg_gtt--1.0.sql
+
+LDFLAGS_SL += $(filter -lm, $(LIBS))
+
+NO_INSTALLCHECK = 1
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_pfs
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_gtt/pg_gtt--1.0.sql b/contrib/pg_gtt/pg_gtt--1.0.sql
new file mode 100644
index 0000000..3f794d7
--- /dev/null
+++ b/contrib/pg_gtt/pg_gtt--1.0.sql
@@ -0,0 +1,28 @@
+/* contrib/pg_gtt/pg_gtt--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_gtt" to load this file. \quit
+
+
+CREATE FUNCTION pg_gtt_att_statistic(text, text, int)
+RETURNS setof pg_statistic
+AS 'MODULE_PATHNAME','pg_gtt_att_statistic'
+LANGUAGE C STRICT;
+
+CREATE TYPE relstats_type AS (relpages int4, reltuples float4, relallvisible int4, relfrozenxid xid, relminmxid xid);
+CREATE TYPE rel_vac_type AS (pid int4, relfrozenxid xid, relminmxid xid);
+
+CREATE FUNCTION pg_gtt_relstats(text, text)
+RETURNS relstats_type
+AS 'MODULE_PATHNAME','pg_gtt_relstats'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION pg_gtt_attached_pid(text, text)
+RETURNS setof int4
+AS 'MODULE_PATHNAME','pg_gtt_attached_pid'
+LANGUAGE C STRICT;
+
+CREATE FUNCTION pg_list_gtt_relfrozenxids()
+RETURNS setof rel_vac_type
+AS 'MODULE_PATHNAME','pg_list_gtt_relfrozenxids'
+LANGUAGE C STRICT;
diff --git a/contrib/pg_gtt/pg_gtt.c b/contrib/pg_gtt/pg_gtt.c
new file mode 100644
index 0000000..19e1ec0
--- /dev/null
+++ b/contrib/pg_gtt/pg_gtt.c
@@ -0,0 +1,474 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_gtt.c
+ *	code to management function for global temparary table
+ *
+ * IDENTIFICATION
+ *	  contrib/pg_gtt/pg_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include <unistd.h>
+#include <sys/time.h>
+#include "port.h"
+#include "access/htup_details.h"
+#include "access/table.h"
+#include "access/xlog.h"
+#include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_class.h"
+#include "funcapi.h"
+#include "storage/ipc.h"
+#include "storage/shmem.h"
+#include "storage/lwlock.h"
+#include "storage/procarray.h"
+#include "storage/proc.h"
+#include "tcop/utility.h"
+#include "utils/builtins.h"
+#include "utils/memutils.h"
+#include "utils/timeout.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include <nodes/makefuncs.h>
+#include "storage/sinvaladt.h"
+#include "miscadmin.h"
+
+PG_MODULE_MAGIC;
+
+static Oid pg_get_relid(const char *relname, char *schema);
+
+Datum pg_gtt_att_statistic(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(pg_gtt_att_statistic);
+
+Datum pg_gtt_relstats(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(pg_gtt_relstats);
+
+Datum pg_gtt_attached_pid(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(pg_gtt_attached_pid);
+
+Datum pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(pg_list_gtt_relfrozenxids);
+
+void		_PG_init(void);
+void		_PG_fini(void);
+
+void
+_PG_init(void)
+{
+	return;
+}
+
+void
+_PG_fini(void)
+{
+	return;
+}
+
+static Oid
+pg_get_relid(const char *relname, char *schema)
+{
+	Oid			relid;
+	RangeVar   *rv = makeRangeVar(schema, (char *)relname, -1);
+
+	relid = RangeVarGetRelid(rv, NoLock, true);
+
+	pfree(rv);
+	return relid;
+}
+
+Datum
+pg_gtt_att_statistic(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc		tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple		tuple;
+	Relation	rel = NULL;
+	char	*relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	*schema = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	int		attnum = PG_GETARG_INT32(2);
+	Oid		reloid;
+	char	rel_persistence;
+
+	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")));
+
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = NULL;
+	rsinfo->setDesc = NULL;
+
+	tupdesc = CreateTemplateTupleDesc(31);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "starelid",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "staattnum",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "stainherit",
+						BOOLOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "stanullfrac",
+						FLOAT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "stawidth",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 6, "stadistinct",
+						FLOAT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 7, "stakind1",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 8, "stakind2",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 9, "stakind3",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 10, "stakind4",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 11, "stakind5",
+						INT2OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 12, "staop1",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 13, "staop2",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 14, "staop3",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 15, "staop4",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 16, "staop5",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 17, "stacoll1",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 18, "stacoll2",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 19, "stacoll3",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 20, "stacoll4",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 21, "stacoll5",
+						OIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 22, "stanumbers1",
+						FLOAT4ARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 23, "stanumbers2",
+						FLOAT4ARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 24, "stanumbers3",
+						FLOAT4ARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 25, "stanumbers4",
+						FLOAT4ARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 26, "stanumbers5",
+						FLOAT4ARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 27, "stavalues1",
+						ANYARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 28, "stavalues2",
+						ANYARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 29, "stavalues3",
+						ANYARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 30, "stavalues4",
+						ANYARRAYOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 31, "stavalues5",
+						ANYARRAYOID, -1, 0);
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (schema == NULL)
+		schema = "public";
+
+	reloid = pg_get_relid(relname, schema);
+	if (reloid == InvalidOid)
+	{
+		elog(WARNING, "relation %s.%s does not exist", schema, relname);
+		return (Datum) 0;
+	}
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation %s.%s not global temp table", schema, relname);
+		return (Datum) 0;
+	}
+
+	tuple = get_gtt_att_statistic(reloid, attnum, false);
+	if (tuple)
+	{
+		HeapTuple tp = heap_copytuple(tuple);
+		tuplestore_puttuple(tupstore, tp);
+	}
+	tuplestore_donestoring(tupstore);
+
+	table_close(rel, NoLock);
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_relstats(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	char	*relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	*schema = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	Oid	reloid;
+	char	rel_persistence;
+	Datum	values[5];
+	bool	isnull[5];
+	BlockNumber	relpages = 0;
+	double		reltuples = 0;
+	BlockNumber	relallvisible = 0;
+	uint32		relfrozenxid = 0;
+	uint32		relminmxid = 0;
+	Relation	rel = NULL;
+
+	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")));
+
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = NULL;
+	rsinfo->setDesc = NULL;
+
+	tupdesc = CreateTemplateTupleDesc(5);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relpages",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "reltuples",
+						FLOAT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relallvisible",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 4, "relfrozenxid",
+						XIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 5, "relminmxid",
+						XIDOID, -1, 0);
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (schema == NULL)
+		schema = "public";
+
+	reloid = pg_get_relid(relname, schema);
+	if (reloid == InvalidOid)
+	{
+		elog(WARNING, "relation %s.%s does not exist", schema, relname);
+		return (Datum) 0;
+	}
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation %s.%s not global temp table", schema, relname);
+		table_close(rel, NoLock);
+		return (Datum) 0;
+	}
+
+	get_gtt_relstats(reloid,
+					&relpages, &reltuples, &relallvisible,
+					&relfrozenxid, &relminmxid);
+
+	memset(isnull, false, sizeof(isnull));
+	memset(values, 0, sizeof(values));
+	values[0] = Int32GetDatum(relpages);
+	values[1] = Float4GetDatum((float4)reltuples);
+	values[2] = Int32GetDatum(relallvisible);
+	values[3] = UInt32GetDatum(relfrozenxid);
+	values[4] = UInt32GetDatum(relminmxid);
+	tuple = heap_form_tuple(tupdesc, values, isnull);
+	tuplestore_puttuple(tupstore, tuple);
+	tuplestore_donestoring(tupstore);
+
+	table_close(rel, NoLock);
+	return (Datum) 0;
+}
+
+Datum
+pg_gtt_attached_pid(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	char	*relname = text_to_cstring(PG_GETARG_TEXT_PP(0));
+	char	*schema = text_to_cstring(PG_GETARG_TEXT_PP(1));
+	Oid	reloid;
+	char	rel_persistence;
+	Datum	values[1];
+	bool	isnull[1];
+	Relation	rel = NULL;
+	PGPROC		*proc = NULL;
+	Bitmapset	*map = NULL;
+	pid_t		pid = 0;
+	int			backendid = 0;
+
+	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")));
+
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = NULL;
+	rsinfo->setDesc = NULL;
+
+	tupdesc = CreateTemplateTupleDesc(1);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid",
+						INT4OID, -1, 0);
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (schema == NULL)
+		schema = "public";
+
+	reloid = pg_get_relid(relname, schema);
+	if (reloid == InvalidOid)
+	{
+		elog(WARNING, "relation %s.%s does not exist", schema, relname);
+		return (Datum) 0;
+	}
+
+	rel = table_open(reloid, AccessShareLock);
+	rel_persistence = get_rel_persistence(reloid);
+	if (rel_persistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		elog(WARNING, "relation %s.%s not global temp table", schema, relname);
+		return (Datum) 0;
+	}
+
+	map = copy_active_gtt_bitmap(rel->rd_node);
+	if (map)
+	{
+		backendid = bms_first_member(map);
+
+		do
+		{
+			proc = BackendIdGetProc(backendid);
+			pid = proc->pid;
+			if (pid > 0)
+			{
+				memset(isnull, false, sizeof(isnull));
+				memset(values, 0, sizeof(values));
+				values[0] = Int32GetDatum(pid);
+				tuple = heap_form_tuple(tupdesc, values, isnull);
+				tuplestore_puttuple(tupstore, tuple);
+			}
+			backendid = bms_next_member(map, backendid);
+		} while (backendid > 0);
+
+		pfree(map);
+	}
+
+	tuplestore_donestoring(tupstore);
+	table_close(rel, NoLock);
+
+	return (Datum) 0;
+}
+
+Datum
+pg_list_gtt_relfrozenxids(PG_FUNCTION_ARGS)
+{
+	ReturnSetInfo	*rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+	TupleDesc	tupdesc;
+	Tuplestorestate *tupstore;
+	MemoryContext	oldcontext;
+	HeapTuple	tuple;
+	Datum	values[3];
+	bool	isnull[3];
+	int			num_xid = MaxBackends + 1;
+	int			*pids = NULL;
+	uint32		*xids = NULL;
+	int			i = 0;
+	int			j = 0;
+	uint32		oldest = 0;
+
+	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")));
+
+	rsinfo->returnMode = SFRM_Materialize;
+	rsinfo->setResult = NULL;
+	rsinfo->setDesc = NULL;
+
+	tupdesc = CreateTemplateTupleDesc(3);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 1, "pid",
+						INT4OID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 2, "relfrozenxid",
+						XIDOID, -1, 0);
+	TupleDescInitEntry(tupdesc, (AttrNumber) 3, "relminmxid",
+						XIDOID, -1, 0);
+
+	oldcontext = MemoryContextSwitchTo(
+			rsinfo->econtext->ecxt_per_query_memory);
+	tupstore = tuplestore_begin_heap(true, false, work_mem);
+	rsinfo->setResult = tupstore;
+	rsinfo->setDesc = tupdesc;
+	MemoryContextSwitchTo(oldcontext);
+
+	if (max_active_gtt <= 0)
+		return (Datum) 0;
+
+	if (RecoveryInProgress())
+		return (Datum) 0;
+
+	pids = palloc0(sizeof(int) * num_xid);
+	xids = palloc0(sizeof(int) * num_xid);
+	oldest = list_all_session_gtt_frozenxids(num_xid, pids, xids, &i);
+	if (i > 0)
+	{
+		if (i > 0)
+		{
+			pids[i] = 0;
+			xids[i] = oldest;
+			i++;
+		}
+
+		for(j = 0; j < i; j++)
+		{
+			memset(isnull, false, sizeof(isnull));
+			memset(values, 0, sizeof(values));
+			values[0] = Int32GetDatum(pids[j]);
+			values[1] = UInt32GetDatum(xids[j]);
+			values[2] = UInt32GetDatum(0);
+			tuple = heap_form_tuple(tupdesc, values, isnull);
+			tuplestore_puttuple(tupstore, tuple);
+		}
+	}
+	tuplestore_donestoring(tupstore);
+	pfree(pids);
+	pfree(xids);
+
+	return (Datum) 0;
+}
+
diff --git a/contrib/pg_gtt/pg_gtt.control b/contrib/pg_gtt/pg_gtt.control
new file mode 100644
index 0000000..342af1d
--- /dev/null
+++ b/contrib/pg_gtt/pg_gtt.control
@@ -0,0 +1,4 @@
+comment = 'pg_gtt'
+default_version = '1.0'
+module_pathname = '$libdir/pg_gtt'
+relocatable = true
diff --git a/src/backend/access/common/reloptions.c b/src/backend/access/common/reloptions.c
index d8790ad..7e30039 100644
--- a/src/backend/access/common/reloptions.c
+++ b/src/backend/access/common/reloptions.c
@@ -158,6 +158,19 @@ static relopt_bool boolRelOpts[] =
 		},
 		true
 	},
+	/*
+	 * For global temp table only
+	 * use AccessExclusiveLock for ensure safety
+	 */
+	{
+		{
+			"on_commit_delete_rows",
+			"global temp table on commit options",
+			RELOPT_KIND_HEAP,
+			ShareUpdateExclusiveLock
+		},
+		true
+	},	
 	/* list terminator */
 	{{NULL}}
 };
@@ -1475,6 +1488,8 @@ bytea *
 default_reloptions(Datum reloptions, bool validate, relopt_kind kind)
 {
 	static const relopt_parse_elt tab[] = {
+		{"on_commit_delete_rows", RELOPT_TYPE_BOOL,
+		offsetof(StdRdOptions, on_commit_delete_rows)},
 		{"fillfactor", RELOPT_TYPE_INT, offsetof(StdRdOptions, fillfactor)},
 		{"autovacuum_enabled", RELOPT_TYPE_BOOL,
 		offsetof(StdRdOptions, autovacuum) + offsetof(AutoVacOpts, enabled)},
diff --git a/src/backend/access/gist/gistutil.c b/src/backend/access/gist/gistutil.c
index a23dec7..79bdb26 100644
--- a/src/backend/access/gist/gistutil.c
+++ b/src/backend/access/gist/gistutil.c
@@ -1014,7 +1014,9 @@ gistGetFakeLSN(Relation rel)
 {
 	static XLogRecPtr counter = FirstNormalUnloggedLSN;
 
-	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+	/* global temp is same as local temp table */
+	if (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		rel->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 	{
 		/*
 		 * Temporary relations are only accessible in our session, so a simple
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 5cc30da..49b7d2e 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -148,7 +148,9 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * metapage, nor the first bitmap page.
 	 */
 	sort_threshold = (maintenance_work_mem * 1024L) / BLCKSZ;
-	if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP)
+	/* global temp table is same as local temp table */
+	if (!(index->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		index->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		sort_threshold = Min(sort_threshold, NBuffers);
 	else
 		sort_threshold = Min(sort_threshold, NLocBuffer);
diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c
index 2dd8821..24c56b9 100644
--- a/src/backend/access/heap/heapam_handler.c
+++ b/src/backend/access/heap/heapam_handler.c
@@ -601,7 +601,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
@@ -654,7 +654,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 a3c4a1d..03fac55 100644
--- a/src/backend/access/heap/vacuumlazy.c
+++ b/src/backend/access/heap/vacuumlazy.c
@@ -60,6 +60,7 @@
 #include "utils/pg_rusage.h"
 #include "utils/timestamp.h"
 
+#include "catalog/storage_gtt.h"
 
 /*
  * Space/time tradeoff parameters: do these need to be user-tunable?
@@ -214,8 +215,10 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 	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));
+	Assert((RELATION_IS_GLOBAL_TEMP(onerel) && onerel->rd_rel->relfrozenxid == InvalidTransactionId) ||
+		(!RELATION_IS_GLOBAL_TEMP(onerel) && TransactionIdIsNormal(onerel->rd_rel->relfrozenxid)));
+	Assert((RELATION_IS_GLOBAL_TEMP(onerel) && onerel->rd_rel->relminmxid == InvalidMultiXactId) ||
+		(!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)
@@ -274,8 +277,19 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
 
 	vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
 
-	vacrelstats->old_rel_pages = onerel->rd_rel->relpages;
-	vacrelstats->old_live_tuples = onerel->rd_rel->reltuples;
+	/* get relstat from gtt localhash */
+	if (RELATION_IS_GLOBAL_TEMP(onerel))
+	{
+		get_gtt_relstats(RelationGetRelid(onerel),
+						&vacrelstats->old_rel_pages,
+						&vacrelstats->old_live_tuples,
+						NULL, NULL, NULL);
+	}
+	else
+	{
+		vacrelstats->old_rel_pages = onerel->rd_rel->relpages;
+		vacrelstats->old_live_tuples = onerel->rd_rel->reltuples;
+	}
 	vacrelstats->num_index_scans = 0;
 	vacrelstats->pages_removed = 0;
 	vacrelstats->lock_waiter_detected = false;
diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c
index 268f869..8b2e610 100644
--- a/src/backend/access/nbtree/nbtpage.c
+++ b/src/backend/access/nbtree/nbtpage.c
@@ -763,7 +763,14 @@ _bt_getbuf(Relation rel, BlockNumber blkno, int access)
 		/* Read an existing block of the relation */
 		buf = ReadBuffer(rel, blkno);
 		LockBuffer(buf, access);
-		_bt_checkpage(rel, buf);
+
+		/* global temp table may be not yet initialized for this backend. */
+		if (RELATION_IS_GLOBAL_TEMP(rel) &&
+			blkno == BTREE_METAPAGE &&
+			PageIsNew(BufferGetPage(buf)))
+			_bt_initmetapage(BufferGetPage(buf), P_NONE, 0);
+		else
+			_bt_checkpage(rel, buf);
 	}
 	else
 	{
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 2e3cc51..b8a57b6 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -6314,6 +6314,10 @@ StartupXLOG(void)
 	else
 		recoveryTargetTLI = ControlFile->checkPointCopy.ThisTimeLineID;
 
+	/* clean temp relation files */
+	if (max_active_gtt > 0)
+		RemovePgTempFiles();
+
 	/*
 	 * Check for signal files, and if so set up state for offline recovery
 	 */
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index a511532..665b7fb 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -44,6 +44,8 @@ OBJS = \
 	storage.o \
 	toasting.o
 
+OBJS += storage_gtt.o
+
 BKIFILES = postgres.bki postgres.description postgres.shdescription
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 1af31c2..ecb516e 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -399,7 +399,9 @@ GetNewRelFileNode(Oid reltablespace, Relation pg_class, char relpersistence)
 
 	switch (relpersistence)
 	{
+		/* global temp table is same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			break;
 		case RELPERSISTENCE_UNLOGGED:
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index b7bcdd9..6e089dd 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"
@@ -100,6 +101,7 @@ static void AddNewRelationTuple(Relation pg_class_desc,
 								Oid reloftype,
 								Oid relowner,
 								char relkind,
+								char relpersistence,
 								TransactionId relfrozenxid,
 								TransactionId relminmxid,
 								Datum relacl,
@@ -405,6 +407,10 @@ heap_create(const char *relname,
 									 relpersistence,
 									 relkind);
 
+	/* global temp table not create storage file when catalog create */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		create_storage = false;
+
 	/*
 	 * Have the storage manager create the relation's disk file, if needed.
 	 *
@@ -428,7 +434,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:
@@ -925,6 +931,7 @@ AddNewRelationTuple(Relation pg_class_desc,
 					Oid reloftype,
 					Oid relowner,
 					char relkind,
+					char relpersistence,
 					TransactionId relfrozenxid,
 					TransactionId relminmxid,
 					Datum relacl,
@@ -963,8 +970,18 @@ AddNewRelationTuple(Relation pg_class_desc,
 			break;
 	}
 
-	new_rel_reltup->relfrozenxid = relfrozenxid;
-	new_rel_reltup->relminmxid = relminmxid;
+	/* global temp table not remember transaction info in catalog */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		new_rel_reltup->relfrozenxid = InvalidTransactionId;
+		new_rel_reltup->relminmxid = InvalidMultiXactId;
+	}
+	else
+	{
+		new_rel_reltup->relfrozenxid = relfrozenxid;
+		new_rel_reltup->relminmxid = relminmxid;
+	}
+
 	new_rel_reltup->relowner = relowner;
 	new_rel_reltup->reltype = new_type_oid;
 	new_rel_reltup->reloftype = reloftype;
@@ -1326,6 +1343,7 @@ heap_create_with_catalog(const char *relname,
 						reloftypeid,
 						ownerid,
 						relkind,
+						relpersistence,
 						relfrozenxid,
 						relminmxid,
 						PointerGetDatum(relacl),
@@ -1410,11 +1428,15 @@ heap_create_with_catalog(const char *relname,
 	 */
 	StoreConstraints(new_rel_desc, cooked_constraints, is_internal);
 
-	/*
-	 * If there's a special on-commit action, remember it
-	 */
-	if (oncommit != ONCOMMIT_NOOP)
-		register_on_commit_action(relid, oncommit);
+	/* global temp table register action when storage init */
+	if (relpersistence != RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		/*
+		 * If there's a special on-commit action, remember it
+		 */
+		if (oncommit != ONCOMMIT_NOOP)
+			register_on_commit_action(relid, oncommit);
+	}
 
 	/*
 	 * ok, the relation has been cataloged, so close our relations and return
@@ -1906,6 +1928,13 @@ heap_drop_with_catalog(Oid relid)
 	if (relid == defaultPartOid)
 		update_default_partition_oid(parentOid, InvalidOid);
 
+	/* We allow to drop global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		if (is_other_backend_use_gtt(rel->rd_node))
+			elog(ERROR, "can not drop relation when other backend attached this global temp table");
+	}
+
 	/*
 	 * Schedule unlinking of the relation's physical files at commit.
 	 */
@@ -3132,9 +3161,10 @@ RemoveStatistics(Oid relid, AttrNumber attnum)
  *
  * The routine will truncate and then reconstruct the indexes on
  * the specified relation.  Caller must hold exclusive lock on rel.
+ *
  */
 static void
-RelationTruncateIndexes(Relation heapRelation)
+RelationTruncateIndexes(Relation heapRelation, LOCKMODE lockmode)
 {
 	ListCell   *indlist;
 
@@ -3146,7 +3176,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 */
 		indexInfo = BuildIndexInfo(currentIndex);
@@ -3185,8 +3215,13 @@ heap_truncate(List *relids)
 	{
 		Oid			rid = lfirst_oid(cell);
 		Relation	rel;
+		LOCKMODE	lockmode = AccessExclusiveLock;
 
-		rel = table_open(rid, AccessExclusiveLock);
+		/* truncate global temp table only need RowExclusiveLock */
+		if (get_rel_persistence(rid) == RELPERSISTENCE_GLOBAL_TEMP)
+			lockmode = RowExclusiveLock;
+
+		rel = table_open(rid, lockmode);
 		relations = lappend(relations, rel);
 	}
 
@@ -3219,6 +3254,8 @@ void
 heap_truncate_one_rel(Relation rel)
 {
 	Oid			toastrelid;
+	LOCKMODE	lockmode = AccessExclusiveLock;
+	bool		truncate_toastrel = false;
 
 	/*
 	 * Truncate the relation.  Partitioned tables have no storage, so there is
@@ -3227,23 +3264,40 @@ heap_truncate_one_rel(Relation rel)
 	if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
 		return;
 
+	toastrelid = rel->rd_rel->reltoastrelid;
+
+	/*
+	 * Truncate global temp table only need RowExclusiveLock
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		lockmode = RowExclusiveLock;
+		if (OidIsValid(toastrelid) &&
+			gtt_storage_attached(toastrelid))
+			truncate_toastrel = true;
+	}
+	else if (OidIsValid(toastrelid))
+		truncate_toastrel = true;
+
 	/* Truncate the underlying relation */
 	table_relation_nontransactional_truncate(rel);
 
 	/* If the relation has indexes, truncate the indexes too */
-	RelationTruncateIndexes(rel);
+	RelationTruncateIndexes(rel, lockmode);
 
 	/* If there is a toast table, truncate that too */
-	toastrelid = rel->rd_rel->reltoastrelid;
-	if (OidIsValid(toastrelid))
+	if (truncate_toastrel)
 	{
-		Relation	toastrel = table_open(toastrelid, AccessExclusiveLock);
+		Relation	toastrel = table_open(toastrelid, lockmode);
 
 		table_relation_nontransactional_truncate(toastrel);
-		RelationTruncateIndexes(toastrel);
+		RelationTruncateIndexes(toastrel, lockmode);
 		/* keep the lock... */
 		table_close(toastrel, NoLock);
 	}
+
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+		up_gtt_relstats(rel, 0, 0, 0, RecentXmin, InvalidMultiXactId);
 }
 
 /*
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 7c34509..f623626 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
+#include "catalog/storage_gtt.h"
 #include "commands/event_trigger.h"
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
@@ -879,6 +880,26 @@ index_create(Relation heapRelation,
 						indexRelationName, RelationGetRelationName(heapRelation))));
 	}
 
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		if (accessMethodObjectId != BTREE_AM_OID)
+			elog(ERROR, "only support btree index on global temp table");
+
+		/* We allow to create index on global temp table only this session use it */
+		if (is_other_backend_use_gtt(heapRelation->rd_node) ||
+			gtt_storage_attached(heapRelation->rd_node.relNode))
+			elog(ERROR, "can not create index when have one or more backend attached this global temp table");
+
+		/* No support create index on global temp table use concurrent mode yet */
+		if (concurrent)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("cannot create indexes on global temporary tables using concurrent mode")));
+
+		/* if global temp table not init storage, then skip build index */
+		flags |= INDEX_CREATE_SKIP_BUILD;
+	}
+
 	/*
 	 * construct tuple descriptor for index tuples
 	 */
@@ -2047,6 +2068,13 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	CheckTableNotInUse(userIndexRelation, "DROP INDEX");
 
+	/* We allow to drop index on global temp table only this session use it */
+	if (RELATION_IS_GLOBAL_TEMP(userHeapRelation))
+	{
+		if (is_other_backend_use_gtt(userHeapRelation->rd_node))
+			elog(ERROR, "can not drop index when other backend attached this global temp table");
+	}
+
 	/*
 	 * Drop Index Concurrently is more or less the reverse process of Create
 	 * Index Concurrently.
@@ -2688,20 +2716,29 @@ index_update_stats(Relation rel,
 		else					/* don't bother for indexes */
 			relallvisible = 0;
 
-		if (rd_rel->relpages != (int32) relpages)
+		/* update index stats into localhash for global temp table */
+		if (RELATION_IS_GLOBAL_TEMP(rel))
 		{
-			rd_rel->relpages = (int32) relpages;
-			dirty = true;
+			up_gtt_relstats(rel, relpages, reltuples, relallvisible,
+							InvalidTransactionId, InvalidMultiXactId);
 		}
-		if (rd_rel->reltuples != (float4) reltuples)
-		{
-			rd_rel->reltuples = (float4) reltuples;
-			dirty = true;
-		}
-		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;
+			}
 		}
 	}
 
@@ -2816,6 +2853,13 @@ index_build(Relation heapRelation,
 		pgstat_progress_update_multi_param(6, index, val);
 	}
 
+	/* POALR */
+	if (RELATION_IS_GLOBAL_TEMP(indexRelation))
+	{
+		if (!gtt_storage_attached(indexRelation->rd_node.relNode))
+			RelationCreateStorage(indexRelation->rd_node, RELPERSISTENCE_GLOBAL_TEMP, indexRelation);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -3148,12 +3192,22 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	/*
 	 * Scan the index and gather up all the TIDs into a tuplesort object.
 	 */
+	memset(&ivinfo, 0, sizeof(IndexVacuumInfo));
 	ivinfo.index = indexRelation;
 	ivinfo.analyze_only = false;
 	ivinfo.report_progress = true;
 	ivinfo.estimated_count = true;
 	ivinfo.message_level = DEBUG2;
-	ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples;
+
+	/* get relstats abort global temp table from hashtable, not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(heapRelation))
+	{
+		get_gtt_relstats(RelationGetRelid(heapRelation),
+						NULL, &ivinfo.num_heap_tuples, NULL,
+						NULL, NULL);
+	}
+	else
+		ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples;
 	ivinfo.strategy = NULL;
 
 	/*
@@ -3411,6 +3465,15 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 				 errmsg("cannot reindex temporary tables of other sessions")));
 
 	/*
+	 * Because global temp table cannot change relfilenode
+	 * no support reindex on global temp table yet.
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(iRel))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("cannot reindex global temporary tables")));
+
+	/*
 	 * Also check for active uses of the index in the current transaction; we
 	 * don't want to reindex underneath an open indexscan.
 	 */
diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c
index e251f5a..97bf247 100644
--- a/src/backend/catalog/namespace.c
+++ b/src/backend/catalog/namespace.c
@@ -647,6 +647,13 @@ RangeVarAdjustRelationPersistence(RangeVar *newRelation, Oid nspid)
 							 errmsg("cannot create temporary relation in non-temporary schema")));
 			}
 			break;
+		/* global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			if (isAnyTempNamespace(nspid))
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+						 errmsg("cannot create global temp relations in temporary schemas")));
+			break;
 		case RELPERSISTENCE_PERMANENT:
 			if (isTempOrTempToastNamespace(nspid))
 				newRelation->relpersistence = RELPERSISTENCE_TEMP;
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 625af8d..baf7d61 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -28,6 +28,7 @@
 #include "access/xlogutils.h"
 #include "catalog/storage.h"
 #include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
 #include "storage/freespace.h"
 #include "storage/smgr.h"
 #include "utils/memutils.h"
@@ -74,9 +75,10 @@ static PendingRelDelete *pendingDeletes = NULL; /* head of linked list */
  *
  * This function is transactional. The creation is WAL-logged, and if the
  * 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;
@@ -86,6 +88,8 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	switch (relpersistence)
 	{
 		case RELPERSISTENCE_TEMP:
+		/* global temp table use same storage strategy as local temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			backend = BackendIdForTempRelations();
 			needs_wal = false;
 			break;
@@ -118,6 +122,10 @@ RelationCreateStorage(RelFileNode rnode, char relpersistence)
 	pending->next = pendingDeletes;
 	pendingDeletes = pending;
 
+	/* remember global temp table storage info to localhash */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP && rel)
+		remember_gtt_storage_info(rnode, rel);
+
 	return srel;
 }
 
@@ -487,8 +495,15 @@ smgrDoPendingDeletes(bool isCommit)
 		smgrdounlinkall(srels, nrels, false);
 
 		for (i = 0; i < nrels; i++)
+		{
 			smgrclose(srels[i]);
 
+			/* clean global temp table flags when transaction commit or rollback */
+			if (SmgrIsTemp(srels[i]) && 
+				gtt_storage_attached(srels[i]->smgr_rnode.node.relNode))
+				forget_gtt_storage_info(srels[i]->smgr_rnode.node.relNode);
+		}
+
 		pfree(srels);
 	}
 }
diff --git a/src/backend/catalog/storage_gtt.c b/src/backend/catalog/storage_gtt.c
new file mode 100644
index 0000000..c7b9a48
--- /dev/null
+++ b/src/backend/catalog/storage_gtt.c
@@ -0,0 +1,810 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.c
+ *	  code to create and destroy physical storage for global temparary table
+ *
+ * IDENTIFICATION
+ *	  src/backend/catalog/storage_gtt.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/visibilitymap.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "access/xloginsert.h"
+#include "access/xlogutils.h"
+#include "access/htup_details.h"
+#include "access/multixact.h"
+#include "catalog/storage.h"
+#include "catalog/storage_xlog.h"
+#include "catalog/storage_gtt.h"
+#include "catalog/heap.h"
+#include "commands/tablecmds.h"
+#include "nodes/primnodes.h"
+#include "nodes/pg_list.h"
+#include "miscadmin.h"
+#include "storage/freespace.h"
+#include "storage/smgr.h"
+#include "storage/ipc.h"
+#include "storage/proc.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/hsearch.h"
+#include "utils/catcache.h"
+#include <utils/relcache.h>
+#include "utils/inval.h"
+#include "utils/guc.h"
+#include "utils/guc.h"
+
+
+/* Copy from bitmapset.c, because gtt used the function in bitmapset.c */
+#define WORDNUM(x)	((x) / BITS_PER_BITMAPWORD)
+#define BITNUM(x)	((x) % BITS_PER_BITMAPWORD)
+
+#define BITMAPSET_SIZE(nwords)	\
+	(offsetof(Bitmapset, words) + (nwords) * sizeof(bitmapword))
+
+static bool gtt_cleaner_exit_registered = false;
+static HTAB *gtt_storage_local_hash = NULL;
+static HTAB *active_gtt_shared_hash = NULL;
+static MemoryContext gtt_relstats_context = NULL;
+
+/* relfrozenxid of all gtts in the current session */
+static List *gtt_session_relfrozenxid_list = NIL;
+static TransactionId gtt_session_frozenxid = InvalidTransactionId;
+
+typedef struct gtt_ctl_data
+{
+	LWLock			lock;
+	int			max_entry;
+	int			entry_size;
+}gtt_ctl_data;
+
+static gtt_ctl_data *gtt_shared_ctl = NULL;
+
+typedef struct
+{
+	RelFileNode	rnode;
+	Bitmapset	*map;
+	/* bitmap data */
+} gtt_shared_hash_entry;
+
+typedef struct
+{
+	Oid			relid;
+
+	Oid			spcnode;
+	/* pg_class stat */
+	int32		relpages;
+	float4		reltuples;
+	int32		relallvisible;
+	TransactionId relfrozenxid;
+	TransactionId relminmxid;
+	char		relkind;
+	bool		on_commit_delete;
+
+	/* pg_statistic */
+	int			natts;
+	int			*attnum;
+	HeapTuple	*att_stat_tups;
+} gtt_local_hash_entry;
+
+static Size action_gtt_shared_hash_entry_size(void);
+static void gtt_storage_checkin(RelFileNode rnode);
+static void gtt_storage_checkout(RelFileNode rnode, bool skiplock);
+static void gtt_storage_removeall(int code, Datum arg);
+static void insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid);
+static void remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid);
+static void set_gtt_session_relfrozenxid(void);
+
+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(&gtt_shared_ctl->lock, LWTRANCHE_GTT_CTL);
+		gtt_shared_ctl->max_entry = max_active_gtt;
+		gtt_shared_ctl->entry_size = action_gtt_shared_hash_entry_size();
+	}
+
+	info.keysize = sizeof(RelFileNode);
+	info.entrysize = gtt_shared_ctl->entry_size;
+	active_gtt_shared_hash =
+		ShmemInitHash("active gtt shared hash",
+						gtt_shared_ctl->max_entry,
+						gtt_shared_ctl->max_entry,
+						&info, HASH_ELEM | HASH_BLOBS | HASH_FIXED_SIZE);
+}
+
+static void
+gtt_storage_checkin(RelFileNode rnode)
+{
+	gtt_shared_hash_entry	*entry;
+	bool			found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+	entry = (gtt_shared_hash_entry *) hash_search(active_gtt_shared_hash,
+												&rnode, HASH_ENTER_NULL, &found);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		ereport(ERROR,
+				(errcode(ERRCODE_OUT_OF_MEMORY),
+				 errmsg("out of shared memory"),
+				 errhint("You might need to increase max_active_gtt.")));
+	}
+
+	if (found == false)
+	{
+		int			wordnum;
+
+		entry->map = (Bitmapset *)((char *)entry + MAXALIGN(sizeof(gtt_shared_hash_entry)));
+		wordnum = WORDNUM(MaxBackends + 1);
+		memset(entry->map, 0, BITMAPSET_SIZE(wordnum + 1));
+		entry->map->nwords = wordnum + 1;
+	}
+
+	bms_add_member(entry->map, MyBackendId);
+	LWLockRelease(&gtt_shared_ctl->lock);
+}
+
+static void
+gtt_storage_checkout(RelFileNode rnode, bool skiplock)
+{
+	gtt_shared_hash_entry	*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (!skiplock)
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(rnode), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		if (!skiplock)
+			LWLockRelease(&gtt_shared_ctl->lock);
+
+		elog(WARNING, "relfilenode %u/%u/%u not exist in gtt shared hash when forget",
+						rnode.dbNode, rnode.spcNode, rnode.relNode);
+		return;
+	}
+
+	Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+	bms_del_member(entry->map, MyBackendId);
+
+	if (bms_is_empty(entry->map))
+	{
+		if (!hash_search(active_gtt_shared_hash, &rnode, HASH_REMOVE, NULL))
+			elog(PANIC, "gtt shared hash table corrupted");
+	}
+
+	if (!skiplock)
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+	return;
+}
+
+Bitmapset *
+copy_active_gtt_bitmap(RelFileNode node)
+{
+	gtt_shared_hash_entry	*entry;
+	Bitmapset	*map_copy = NULL;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(node), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_shared_ctl->lock);
+		return NULL;
+	}
+
+	Assert(entry->map);
+	if (!bms_is_empty(entry->map))
+		map_copy = bms_copy(entry->map);
+
+	LWLockRelease(&gtt_shared_ctl->lock);
+
+	return map_copy;
+}
+
+bool
+is_other_backend_use_gtt(RelFileNode node)
+{
+	gtt_shared_hash_entry	*entry;
+	bool		in_use = false;
+	int			num_use = 0;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	LWLockAcquire(&gtt_shared_ctl->lock, LW_SHARED);
+	entry = hash_search(active_gtt_shared_hash,
+					(void *) &(node), HASH_FIND, NULL);
+
+	if (entry == NULL)
+	{
+		LWLockRelease(&gtt_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(&gtt_shared_ctl->lock);
+
+	return in_use;
+}
+
+void
+remember_gtt_storage_info(RelFileNode rnode, Relation rel)
+{
+	gtt_local_hash_entry	*entry;
+	MemoryContext			oldcontext;
+	Oid			relid = rnode.relNode;
+	bool		found;
+
+	if (max_active_gtt <= 0)
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temporary table feature is disable"),
+			 errhint("You might need to increase max_active_gtt to enable this feature.")));
+
+	if (RecoveryInProgress())
+		elog(ERROR, "readonly mode not support access global temp table yet");
+
+	if (gtt_storage_local_hash == NULL)
+	{
+#define GTT_LOCAL_HASH_SIZE		1024
+		/* First time through: initialize the hash table */
+		HASHCTL		ctl;
+
+		MemSet(&ctl, 0, sizeof(ctl));
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(gtt_local_hash_entry);
+		gtt_storage_local_hash =
+			hash_create("global temp relation table",
+						GTT_LOCAL_HASH_SIZE,
+						&ctl, HASH_ELEM | HASH_BLOBS);
+
+		if (!CacheMemoryContext)
+			CreateCacheMemoryContext();
+
+		gtt_relstats_context =
+			AllocSetContextCreate(CacheMemoryContext,
+							"gtt relstats context",
+							ALLOCSET_DEFAULT_SIZES);
+	}
+
+	/* Look up or create an entry */
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_ENTER, &found);
+
+	if (found)
+	{
+		elog(ERROR, "backend %d relid %u already exists in global temp table local hash",
+					MyBackendId, relid);
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+
+	entry->spcnode = rnode.spcNode;
+	entry->relpages = 0;
+	entry->reltuples = 0;
+	entry->relallvisible = 0;
+	entry->relkind = rel->rd_rel->relkind;
+	/* only heap contain transaction information */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		int natts = RelationGetNumberOfAttributes(rel);
+		entry->natts = natts;
+		entry->attnum = palloc0(sizeof(int) * natts);
+		entry->att_stat_tups = palloc0(sizeof(HeapTuple) * natts);
+
+		if (RELATION_GTT_ON_COMMIT_DELETE(rel))
+		{
+			entry->on_commit_delete = true;
+			register_on_commit_action(rel->rd_node.relNode, ONCOMMIT_DELETE_ROWS);
+		}
+
+		entry->relfrozenxid = RecentXmin;
+		entry->relminmxid = GetOldestMultiXactId();
+		insert_gtt_relfrozenxid_to_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+		gtt_storage_checkin(rnode);
+	}
+	else
+	{
+		entry->natts = 0;
+		entry->attnum = 0;
+		entry->att_stat_tups = NULL;
+		entry->on_commit_delete = false;
+		entry->relfrozenxid = 0;
+		entry->relminmxid = 0;
+	}
+
+	MemoryContextSwitchTo(oldcontext);
+
+	if (!gtt_cleaner_exit_registered)
+	{
+		before_shmem_exit(gtt_storage_removeall, 0);
+		gtt_cleaner_exit_registered = true;
+	}
+
+	return;
+}
+
+void
+forget_gtt_storage_info(Oid relid)
+{
+	gtt_local_hash_entry		*entry;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+				(void *) &(relid), HASH_FIND, NULL);
+
+	if (entry && entry->relkind == RELKIND_RELATION)
+	{
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		gtt_storage_checkout(rnode, false);
+
+		Assert(TransactionIdIsNormal(entry->relfrozenxid));
+		remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+		set_gtt_session_relfrozenxid();
+	}
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_REMOVE, NULL);
+
+	return;
+}
+
+/* is the storage file was created in this backend */
+bool
+gtt_storage_attached(Oid relid)
+{
+	bool found;
+
+	if (max_active_gtt <= 0)
+		return false;
+
+	if (gtt_storage_local_hash == NULL)
+		return false;
+
+	hash_search(gtt_storage_local_hash,
+			(void *) &(relid), HASH_FIND, &found);
+
+	return found;
+}
+
+static void
+gtt_storage_removeall(int code, Datum arg)
+{
+	HASH_SEQ_STATUS				status;
+	gtt_local_hash_entry		*entry;
+	int			nrels = 0,
+				maxrels = 0;
+	SMgrRelation	*srels = NULL;
+	RelFileNode		*rnodes = NULL;
+	char			*relkinds = NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	hash_seq_init(&status, gtt_storage_local_hash);
+	while ((entry = (gtt_local_hash_entry *) hash_seq_search(&status)) != NULL)
+	{
+		SMgrRelation srel;
+		RelFileNode rnode;
+
+		rnode.spcNode = entry->spcnode;
+		rnode.dbNode = MyDatabaseId;
+		rnode.relNode = entry->relid;
+
+		srel = smgropen(rnode, MyBackendId);
+
+		/* allocate the initial array, or extend it, if needed */
+		if (maxrels == 0)
+		{
+			maxrels = 32;
+			srels = palloc(sizeof(SMgrRelation) * maxrels);
+			rnodes = palloc(sizeof(RelFileNode) * maxrels);
+			relkinds = palloc(sizeof(char) * maxrels);
+		}
+		else if (maxrels <= nrels)
+		{
+			maxrels *= 2;
+			srels = repalloc(srels, sizeof(SMgrRelation) * maxrels);
+			rnodes = repalloc(rnodes, sizeof(RelFileNode) * maxrels);
+			relkinds = repalloc(relkinds, sizeof(char) * maxrels);
+		}
+
+		srels[nrels] = srel;
+		rnodes[nrels] = rnode;
+		relkinds[nrels] = entry->relkind;
+		nrels++;
+	}
+
+	if (nrels > 0)
+	{
+		int i;
+
+		smgrdounlinkall(srels, nrels, false);
+
+		LWLockAcquire(&gtt_shared_ctl->lock, LW_EXCLUSIVE);
+		for (i = 0; i < nrels; i++)
+		{
+			smgrclose(srels[i]);
+			if (relkinds[i] == RELKIND_RELATION)
+				gtt_storage_checkout(rnodes[i], true);
+		}
+		LWLockRelease(&gtt_shared_ctl->lock);
+
+		pfree(srels);
+		pfree(rnodes);
+		pfree(relkinds);
+	}
+
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;
+
+	return;
+}
+
+/*
+ * Update global temp table relstats(relpage/reltuple/relallvisible) 
+ * to local hashtable
+ */
+void
+up_gtt_relstats(Relation relation,
+					BlockNumber num_pages,
+					double num_tuples,
+					BlockNumber num_all_visible_pages,
+					TransactionId relfrozenxid,
+					TransactionId relminmxid)
+{
+	Oid		relid = RelationGetRelid(relation);
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	Assert(entry->spcnode);
+
+	if (num_pages >= 0 &&
+		entry->relpages != (int32)num_pages)
+		entry->relpages = (int32)num_pages;
+
+	if (num_tuples >= 0 &&
+		num_tuples != (float4)entry->reltuples)
+		entry->reltuples = (float4)num_tuples;
+
+	/* only heap contain transaction information and relallvisible */
+	if (entry->relkind == RELKIND_RELATION)
+	{
+		if (entry->relallvisible >= 0 &&
+			entry->relallvisible != (int32)num_all_visible_pages)
+		{
+			entry->relallvisible = (int32)num_all_visible_pages;
+		}
+
+		if (TransactionIdIsNormal(relfrozenxid) &&
+			entry->relfrozenxid != relfrozenxid &&
+			(TransactionIdPrecedes(entry->relfrozenxid, relfrozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(), entry->relfrozenxid)))
+		{
+			remove_gtt_relfrozenxid_from_ordered_list(entry->relfrozenxid);
+			entry->relfrozenxid = relfrozenxid;
+			insert_gtt_relfrozenxid_to_ordered_list(relfrozenxid);
+			set_gtt_session_relfrozenxid();
+		}
+
+		if (MultiXactIdIsValid(relminmxid) &&
+			entry->relminmxid != relminmxid &&
+			(MultiXactIdPrecedes(entry->relminmxid, relminmxid) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), entry->relminmxid)))
+		{
+			entry->relminmxid = relminmxid;
+		}
+	}
+
+	return;
+}
+
+/*
+ * Search global temp table relstats(relpage/reltuple/relallvisible) 
+ * from local hashtable.
+ */
+void
+get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+				BlockNumber *relallvisible, TransactionId *relfrozenxid,
+				TransactionId *relminmxid)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+					(void *) &relid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	Assert(entry->relid == relid);
+
+	if (relpages)
+		*relpages = entry->relpages;
+
+	if (reltuples)
+		*reltuples = entry->reltuples;
+
+	if (relallvisible)
+		*relallvisible = entry->relallvisible;
+
+	if (relfrozenxid)
+		*relfrozenxid = entry->relfrozenxid;
+
+	if (relminmxid)
+		*relminmxid = entry->relminmxid;
+
+	return;
+}
+
+/*
+ * Update global temp table statistic info(definition is same as pg_statistic)
+ * to local hashtable where ananyze global temp table
+ */
+void
+up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+					TupleDesc tupleDescriptor, Datum *values, bool *isnull)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	MemoryContext			oldcontext;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return;
+
+	if (gtt_storage_local_hash == NULL)
+		return;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return;
+
+	if (entry->relkind != RELKIND_RELATION)
+	{
+		elog(WARNING, "oid %u not a relation", reloid);
+		return;
+	}
+
+	/* todo */
+	if (entry->natts < natts)
+	{
+		elog(WARNING, "reloid %u not support update attstat after add colunm", reloid);
+		return;
+	}
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	Assert(entry->relid == reloid);
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == 0)
+		{
+			entry->attnum[i] = attnum;
+			break;
+		}
+		else if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			heap_freetuple(entry->att_stat_tups[i]);
+			entry->att_stat_tups[i] = NULL;
+			break;
+		}
+	}
+
+	Assert(i < entry->natts);
+	Assert(entry->att_stat_tups[i] == NULL);
+	entry->att_stat_tups[i] = heap_form_tuple(tupleDescriptor, values, isnull);
+	MemoryContextSwitchTo(oldcontext);
+
+	return;
+}
+
+/*
+ * Search global temp table statistic info(definition is same as pg_statistic)
+ * from local hashtable.
+ */
+HeapTuple
+get_gtt_att_statistic(Oid reloid, int attnum, bool inh)
+{
+	gtt_local_hash_entry	*entry;
+	bool					found;
+	int						i = 0;
+
+	if (max_active_gtt <= 0)
+		return NULL;
+
+	if (gtt_storage_local_hash == NULL)
+		return NULL;
+
+	entry = hash_search(gtt_storage_local_hash,
+						(void *) &reloid, HASH_FIND, &found);
+
+	if (!found)
+		return NULL;
+
+	for (i = 0; i < entry->natts; i++)
+	{
+		if (entry->attnum[i] == attnum)
+		{
+			Assert(entry->att_stat_tups[i]);
+			return entry->att_stat_tups[i];
+		}
+	}
+
+	return NULL;
+}
+
+void
+release_gtt_statistic_cache(HeapTuple tup)
+{
+	/* do nothing */
+	return;
+}
+
+static void
+insert_gtt_relfrozenxid_to_ordered_list(Oid relfrozenxid)
+{
+	MemoryContext	oldcontext;
+	ListCell	*cell;
+	int i;
+	Assert(TransactionIdIsNormal(relfrozenxid));
+
+	oldcontext = MemoryContextSwitchTo(gtt_relstats_context);
+	/* Does the datum belong at the front? */
+	if (gtt_session_relfrozenxid_list == NIL ||
+		TransactionIdFollowsOrEquals(relfrozenxid,
+			linitial_oid(gtt_session_relfrozenxid_list)))
+	{
+		gtt_session_relfrozenxid_list =
+			lcons_oid(relfrozenxid, gtt_session_relfrozenxid_list);
+		MemoryContextSwitchTo(oldcontext);
+
+		return;
+	}
+	/* No, so find the entry it belongs after */
+
+	i = 0;
+	foreach (cell, gtt_session_relfrozenxid_list)
+	{
+		if (TransactionIdFollowsOrEquals(relfrozenxid, lfirst_oid(cell)))
+			break;	/* it belongs after 'prev', before 'curr' */
+
+		i += 1;
+	}
+	/* Insert datum into list after 'prev' */
+	list_insert_nth_oid(gtt_session_relfrozenxid_list, i, relfrozenxid);
+	MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+remove_gtt_relfrozenxid_from_ordered_list(Oid relfrozenxid)
+{
+	gtt_session_relfrozenxid_list =
+		list_delete_oid(gtt_session_relfrozenxid_list, relfrozenxid);
+}
+
+static void
+set_gtt_session_relfrozenxid(void)
+{
+	TransactionId gtt_frozenxid = InvalidTransactionId;
+
+	if (gtt_session_relfrozenxid_list)
+		gtt_frozenxid = llast_oid(gtt_session_relfrozenxid_list);
+
+	gtt_session_frozenxid = gtt_frozenxid;
+	if (MyProc->session_gtt_frozenxid != gtt_frozenxid)
+		MyProc->session_gtt_frozenxid = gtt_frozenxid;
+}
+
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7accb95..e0dc376 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -65,6 +65,7 @@
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
 
+#include "catalog/storage_gtt.h"
 
 /* Per-index data for ANALYZE */
 typedef struct AnlIndexData
@@ -102,7 +103,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);
 
@@ -575,14 +576,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]));
 		}
 
 		/*
@@ -659,11 +661,20 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 			IndexBulkDeleteResult *stats;
 			IndexVacuumInfo ivinfo;
 
+			memset(&ivinfo, 0, sizeof(IndexVacuumInfo));
 			ivinfo.index = Irel[ind];
 			ivinfo.analyze_only = true;
 			ivinfo.estimated_count = true;
 			ivinfo.message_level = elevel;
-			ivinfo.num_heap_tuples = onerel->rd_rel->reltuples;
+			/* get global temp relstats from localhash, not catalog */
+			if (RELATION_IS_GLOBAL_TEMP(onerel))
+			{
+				get_gtt_relstats(RelationGetRelid(onerel),
+								NULL, &ivinfo.num_heap_tuples, NULL,
+								NULL, NULL);
+			}
+			else
+				ivinfo.num_heap_tuples = onerel->rd_rel->reltuples;
 			ivinfo.strategy = vac_strategy;
 
 			stats = index_vacuum_cleanup(&ivinfo, NULL);
@@ -1425,7 +1436,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;
@@ -1527,31 +1538,45 @@ update_attstats(Oid relid, bool inh, int natts, VacAttrStats **vacattrstats)
 			}
 		}
 
-		/* Is there already a pg_statistic tuple for this attribute? */
-		oldtup = SearchSysCache3(STATRELATTINH,
-								 ObjectIdGetDatum(relid),
-								 Int16GetDatum(stats->attr->attnum),
-								 BoolGetDatum(inh));
-
-		if (HeapTupleIsValid(oldtup))
+		/* Update column statistic to localhash, not catalog */
+		if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		{
-			/* Yes, replace it */
-			stup = heap_modify_tuple(oldtup,
-									 RelationGetDescr(sd),
-									 values,
-									 nulls,
-									 replaces);
-			ReleaseSysCache(oldtup);
-			CatalogTupleUpdate(sd, &stup->t_self, stup);
+			up_gtt_att_statistic(relid,
+								stats->attr->attnum,
+								inh,
+								natts,
+								RelationGetDescr(sd),
+								values,
+								nulls);
 		}
 		else
 		{
-			/* No, insert new tuple */
-			stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
-			CatalogTupleInsert(sd, stup);
-		}
+			/* Is there already a pg_statistic tuple for this attribute? */
+			oldtup = SearchSysCache3(STATRELATTINH,
+									 ObjectIdGetDatum(relid),
+									 Int16GetDatum(stats->attr->attnum),
+									 BoolGetDatum(inh));
+
+			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);
+			heap_freetuple(stup);
+		}
 	}
 
 	table_close(sd, RowExclusiveLock);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index a23128d..0d38988 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -392,6 +392,12 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
 					 errmsg("cannot vacuum temporary tables of other sessions")));
 	}
 
+	/* not support cluster global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(OldHeap))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support cluster global temporary tables yet")));
+
 	/*
 	 * Also check for active uses of the relation in the current transaction,
 	 * including open scans and pending AFTER trigger events.
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 374e2d0..8e88a59 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -2585,6 +2585,16 @@ ReindexMultipleTables(const char *objectName, ReindexObjectType objectKind,
 			!isTempNamespace(classtuple->relnamespace))
 			continue;
 
+		/* not support reindex on global temp table, so skip it */
+		if (classtuple->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			ereport(WARNING,
+				(errmsg("global temp table \"%s.%s\" skip reindexed",
+					get_namespace_name(get_rel_namespace(relid)),
+					get_rel_name(relid))));
+			continue;
+		}
+
 		/* Check user/system classification, and optionally skip */
 		if (objectKind == REINDEX_OBJECT_SYSTEM &&
 			!IsSystemClass(relid, classtuple))
diff --git a/src/backend/commands/lockcmds.c b/src/backend/commands/lockcmds.c
index bae3b38..ff9d5cb 100644
--- a/src/backend/commands/lockcmds.c
+++ b/src/backend/commands/lockcmds.c
@@ -107,7 +107,8 @@ RangeVarCallbackForLockTable(const RangeVar *rv, Oid relid, Oid oldrelid,
 	 * transaction.
 	 */
 	relpersistence = get_rel_persistence(relid);
-	if (relpersistence == RELPERSISTENCE_TEMP)
+	if (relpersistence == RELPERSISTENCE_TEMP ||
+		relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		MyXactFlags |= XACT_FLAGS_ACCESSEDTEMPNAMESPACE;
 
 	/* Check permissions. */
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5597be6..4bf4b9a 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -48,6 +48,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"
@@ -531,6 +532,7 @@ static void refuseDupeIndexAttach(Relation parentIdx, Relation partIdx,
 								  Relation partitionTbl);
 static List *GetParentedForeignKeyRefs(Relation partition);
 static void ATDetachCheckNoForeignKeyRefs(Relation partition);
+static bool has_oncommit_option(List *options);
 
 
 /* ----------------------------------------------------------------
@@ -576,6 +578,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	LOCKMODE	parentLockmode;
 	const char *accessMethod = NULL;
 	Oid			accessMethodId = InvalidOid;
+	bool		has_oncommit_clause = false;
 
 	/*
 	 * Truncate relname to appropriate length (probably a waste of time, as
@@ -586,8 +589,10 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Check consistency of arguments
 	 */
+	/* global temp table same as local temp table */
 	if (stmt->oncommit != ONCOMMIT_NOOP
-		&& stmt->relation->relpersistence != RELPERSISTENCE_TEMP)
+		&& !(stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+			 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP))
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 				 errmsg("ON COMMIT can only be used on temporary tables")));
@@ -617,12 +622,28 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	 * code.  This is needed because calling code might not expect untrusted
 	 * tables to appear in pg_temp at the front of its search path.
 	 */
-	if (stmt->relation->relpersistence == RELPERSISTENCE_TEMP
+	/* global temp table same as local temp table */
+	if ((stmt->relation->relpersistence == RELPERSISTENCE_TEMP ||
+		 stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 		&& InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 				 errmsg("cannot create temporary table within security-restricted operation")));
 
+	/* Not support partitioned or inherited global temp table yet */
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (relkind == RELKIND_PARTITIONED_TABLE)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support create global temporary partition table yet")));
+
+		if (stmt->inhRelations)
+			ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				errmsg("not support create global temporary inheritance table yet")));
+	}
+
 	/*
 	 * Determine the lockmode to use when scanning parents.  A self-exclusive
 	 * lock is needed here.
@@ -718,6 +739,40 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 	/*
 	 * Parse and validate reloptions, if any.
 	 */
+	/* global temp table */
+	has_oncommit_clause = has_oncommit_option(stmt->options);
+	if (stmt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		if (has_oncommit_clause)
+		{
+			if (stmt->oncommit != ONCOMMIT_NOOP)
+				elog(ERROR, "can not defeine global temp table with on commit and with clause at same time");
+		}
+		else
+		{
+			DefElem *opt = makeNode(DefElem);
+
+			opt->type = T_DefElem;
+			opt->defnamespace = NULL;
+			opt->defname = "on_commit_delete_rows";
+			opt->defaction = DEFELEM_UNSPEC;
+
+			/* use reloptions to remember on commit clause */
+			if (stmt->oncommit == ONCOMMIT_DELETE_ROWS)
+				opt->arg  = (Node *)makeString("true");
+			else if (stmt->oncommit == ONCOMMIT_PRESERVE_ROWS)
+				opt->arg  = (Node *)makeString("false");
+			else if (stmt->oncommit == ONCOMMIT_NOOP)
+				opt->arg  = (Node *)makeString("true");
+			else
+				elog(ERROR, "global temp table not support on commit drop clause");
+
+			stmt->options = lappend(stmt->options, opt);
+		}
+	}
+	else if (has_oncommit_clause)
+		elog(ERROR, "regular table cannot specifie on_commit_delete_rows");
+
 	reloptions = transformRelOptions((Datum) 0, stmt->options, NULL, validnsps,
 									 true, false);
 
@@ -1772,7 +1827,8 @@ ExecuteTruncateGuts(List *explicit_rels, List *relids, List *relids_logged,
 		 * table or the current physical file to be thrown away anyway.
 		 */
 		if (rel->rd_createSubid == mySubid ||
-			rel->rd_newRelfilenodeSubid == mySubid)
+			rel->rd_newRelfilenodeSubid == mySubid ||
+			RELATION_IS_GLOBAL_TEMP(rel))
 		{
 			/* Immediate, non-rollbackable truncation is OK */
 			heap_truncate_one_rel(rel);
@@ -3330,6 +3386,13 @@ RenameRelationInternal(Oid myrelid, const char *newrelname, bool is_internal, bo
 	 * specially.
 	 */
 	targetrelation = relation_open(myrelid, is_index ? ShareUpdateExclusiveLock : AccessExclusiveLock);
+
+	/* not support rename global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(targetrelation))
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("not support rename global temporary tables yet")));
+
 	namespaceId = RelationGetNamespace(targetrelation);
 
 	/*
@@ -3499,6 +3562,14 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
 	/* Caller is required to provide an adequate lock. */
 	rel = relation_open(relid, NoLock);
 
+	/* not support alter global temp table yet */
+	if (RELATION_IS_GLOBAL_TEMP(rel))
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			errmsg("not support alter global temporary tables yet")));
+	}
+
 	CheckTableNotInUse(rel, "ALTER TABLE");
 
 	ATController(stmt, rel, stmt->cmds, stmt->relation->inh, lockmode);
@@ -7679,6 +7750,13 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 				 errmsg("referenced relation \"%s\" is not a table",
 						RelationGetRelationName(pkrel))));
 
+	/* global temp table not support foreign key constraint yet */
+	if (RELATION_IS_GLOBAL_TEMP(pkrel))
+		ereport(ERROR,
+				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+				 errmsg("referenced relation \"%s\" is not a global temp table",
+						RelationGetRelationName(pkrel))));
+
 	if (!allowSystemTableMods && IsSystemRelation(pkrel))
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
@@ -7718,6 +7796,12 @@ ATAddForeignKeyConstraint(List **wqueue, AlteredTableInfo *tab, Relation rel,
 						(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 						 errmsg("constraints on temporary tables must involve temporary tables of this session")));
 			break;
+		/* global temp table not support foreign key constraint yet */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
+					 errmsg("not support foreign key constraints on global temp table yet")));
+			break;
 	}
 
 	/*
@@ -12726,7 +12810,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,
@@ -14133,7 +14217,9 @@ ATPrepChangePersistence(Relation rel, bool toLogged)
 	 */
 	switch (rel->rd_rel->relpersistence)
 	{
+		/* global temp table same as local temp table */
 		case RELPERSISTENCE_TEMP:
+		case RELPERSISTENCE_GLOBAL_TEMP:
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_TABLE_DEFINITION),
 					 errmsg("cannot change logged status of table \"%s\" because it is temporary",
@@ -16723,3 +16809,20 @@ ATDetachCheckNoForeignKeyRefs(Relation partition)
 		table_close(rel, NoLock);
 	}
 }
+
+static bool
+has_oncommit_option(List *options)
+{
+	ListCell   *listptr;
+
+	foreach(listptr, options)
+	{
+		DefElem    *def = (DefElem *) lfirst(listptr);
+
+		if (pg_strcasecmp(def->defname, "on_commit_delete_rows") == 0)
+			return true;
+	}
+
+	return false;
+}
+
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index da1da23..70278a6 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"
@@ -1070,12 +1071,25 @@ vac_estimate_reltuples(Relation relation,
 					   BlockNumber scanned_pages,
 					   double scanned_tuples)
 {
-	BlockNumber old_rel_pages = relation->rd_rel->relpages;
-	double		old_rel_tuples = relation->rd_rel->reltuples;
+	BlockNumber old_rel_pages = 0;
+	double		old_rel_tuples = 0;
 	double		old_density;
 	double		unscanned_pages;
 	double		total_tuples;
 
+	/* get relstat from gtt local hash */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+	{
+		get_gtt_relstats(RelationGetRelid(relation),
+						&old_rel_pages, &old_rel_tuples, NULL,
+						NULL, NULL);
+	}
+	else
+	{
+		old_rel_pages = relation->rd_rel->relpages;
+		old_rel_tuples = relation->rd_rel->reltuples;
+	}
+
 	/* If we did scan the whole table, just use the count as-is */
 	if (scanned_pages >= total_pages)
 		return scanned_tuples;
@@ -1175,24 +1189,8 @@ vac_update_relstats(Relation relation,
 	/* Apply statistical updates, if any, to copied tuple */
 
 	dirty = false;
-	if (pgcform->relpages != (int32) num_pages)
-	{
-		pgcform->relpages = (int32) num_pages;
-		dirty = true;
-	}
-	if (pgcform->reltuples != (float4) num_tuples)
-	{
-		pgcform->reltuples = (float4) num_tuples;
-		dirty = true;
-	}
-	if (pgcform->relallvisible != (int32) num_all_visible_pages)
-	{
-		pgcform->relallvisible = (int32) num_all_visible_pages;
-		dirty = true;
-	}
 
 	/* Apply DDL updates, but not inside an outer transaction (see above) */
-
 	if (!in_outer_xact)
 	{
 		/*
@@ -1217,37 +1215,64 @@ vac_update_relstats(Relation relation,
 		}
 	}
 
-	/*
-	 * Update relfrozenxid, unless caller passed InvalidTransactionId
-	 * indicating it has no new data.
-	 *
-	 * Ordinarily, we don't let relfrozenxid go backwards: if things are
-	 * working correctly, the only way the new frozenxid could be older would
-	 * be if a previous VACUUM was done with a tighter freeze_min_age, in
-	 * which case we don't want to forget the work it already did.  However,
-	 * if the stored relfrozenxid is "in the future", then it must be corrupt
-	 * and it seems best to overwrite it with the cutoff we used this time.
-	 * This should match vac_update_datfrozenxid() concerning what we consider
-	 * to be "in the future".
-	 */
-	if (TransactionIdIsNormal(frozenxid) &&
-		pgcform->relfrozenxid != frozenxid &&
-		(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
-		 TransactionIdPrecedes(ReadNewTransactionId(),
-							   pgcform->relfrozenxid)))
+	/* global temp table remember relstats to localhash not catalog */
+	if (RELATION_IS_GLOBAL_TEMP(relation))
 	{
-		pgcform->relfrozenxid = frozenxid;
-		dirty = true;
+		up_gtt_relstats(relation,
+						num_pages, num_tuples,
+						num_all_visible_pages,
+						frozenxid, minmulti);
 	}
-
-	/* Similarly for relminmxid */
-	if (MultiXactIdIsValid(minmulti) &&
-		pgcform->relminmxid != minmulti &&
-		(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
-		 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
+	else
 	{
-		pgcform->relminmxid = minmulti;
-		dirty = true;
+		if (pgcform->relpages != (int32) num_pages)
+		{
+			pgcform->relpages = (int32) num_pages;
+			dirty = true;
+		}
+		if (pgcform->reltuples != (float4) num_tuples)
+		{
+			pgcform->reltuples = (float4) num_tuples;
+			dirty = true;
+		}
+		if (pgcform->relallvisible != (int32) num_all_visible_pages)
+		{
+			pgcform->relallvisible = (int32) num_all_visible_pages;
+			dirty = true;
+		}
+
+		/*
+		 * Update relfrozenxid, unless caller passed InvalidTransactionId
+		 * indicating it has no new data.
+		 *
+		 * Ordinarily, we don't let relfrozenxid go backwards: if things are
+		 * working correctly, the only way the new frozenxid could be older would
+		 * be if a previous VACUUM was done with a tighter freeze_min_age, in
+		 * which case we don't want to forget the work it already did.  However,
+		 * if the stored relfrozenxid is "in the future", then it must be corrupt
+		 * and it seems best to overwrite it with the cutoff we used this time.
+		 * This should match vac_update_datfrozenxid() concerning what we consider
+		 * to be "in the future".
+		 */
+		if (TransactionIdIsNormal(frozenxid) &&
+			pgcform->relfrozenxid != frozenxid &&
+			(TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid) ||
+			 TransactionIdPrecedes(ReadNewTransactionId(),
+								   pgcform->relfrozenxid)))
+		{
+			pgcform->relfrozenxid = frozenxid;
+			dirty = true;
+		}
+
+		/* Similarly for relminmxid */
+		if (MultiXactIdIsValid(minmulti) &&
+			pgcform->relminmxid != minmulti &&
+			(MultiXactIdPrecedes(pgcform->relminmxid, minmulti) ||
+			 MultiXactIdPrecedes(ReadNextMultiXactId(), pgcform->relminmxid)))
+		{
+			pgcform->relminmxid = minmulti;
+			dirty = true;
+		}
 	}
 
 	/* If anything changed, write out the tuple. */
@@ -1339,6 +1364,10 @@ vac_update_datfrozenxid(void)
 			continue;
 		}
 
+		/* global temp table relstats not in pg_class */
+		if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			continue;
+
 		/*
 		 * Some table AMs might not need per-relation xid / multixid horizons.
 		 * It therefore seems reasonable to allow relfrozenxid and relminmxid
@@ -1396,6 +1425,25 @@ vac_update_datfrozenxid(void)
 	Assert(TransactionIdIsNormal(newFrozenXid));
 	Assert(MultiXactIdIsValid(newMinMulti));
 
+	/*
+	 * Global temp table get frozenxid from MyProc
+	 * to avoid the vacuum truncate clog that gtt need.
+	 */
+	if (max_active_gtt > 0)
+	{
+		TransactionId oldest_gtt_frozenxid =
+			list_all_session_gtt_frozenxids(0, NULL, NULL, NULL);
+
+		if (TransactionIdIsNormal(oldest_gtt_frozenxid) &&
+			TransactionIdPrecedes(oldest_gtt_frozenxid, newFrozenXid))
+		{
+			ereport(WARNING,
+				(errmsg("global temp table oldest FrozenXid is far in the past"),
+				 errhint("please truncate them or kill those sessions that use them.")));
+			newFrozenXid = oldest_gtt_frozenxid;
+		}
+	}
+
 	/* Now fetch the pg_database tuple we need to update. */
 	relation = table_open(DatabaseRelationId, RowExclusiveLock);
 
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index db3a68a..b41a920 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -591,6 +591,8 @@ static void
 set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 						  RangeTblEntry *rte)
 {
+	char relpersistence;
+
 	/*
 	 * The flag has previously been initialized to false, so we can just
 	 * return if it becomes clear that we can't safely set it.
@@ -618,7 +620,11 @@ set_rel_consider_parallel(PlannerInfo *root, RelOptInfo *rel,
 			 * the rest of the necessary infrastructure right now anyway.  So
 			 * for now, bail out if we see a temporary table.
 			 */
-			if (get_rel_persistence(rte->relid) == RELPERSISTENCE_TEMP)
+			/* global temp table is same as local temp table */
+			relpersistence = get_rel_persistence(rte->relid);
+
+			if (relpersistence == RELPERSISTENCE_TEMP ||
+				relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 				return;
 
 			/*
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 17c5f08..fa6aee5 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -6307,7 +6307,9 @@ plan_create_index_workers(Oid tableOid, Oid indexOid)
 	 * Furthermore, any index predicate or index expressions must be parallel
 	 * safe.
 	 */
+	/* global temp table is same as local temp table */
 	if (heap->rd_rel->relpersistence == RELPERSISTENCE_TEMP ||
+		heap->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexExpressions(index)) ||
 		!is_parallel_safe(root, (Node *) RelationGetIndexPredicate(index)))
 	{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index e5f9e04..ecc4543 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -53,6 +53,7 @@
 #include "utils/syscache.h"
 #include "utils/snapmgr.h"
 
+#include "catalog/storage_gtt.h"
 
 /* GUC parameter */
 int			constraint_exclusion = CONSTRAINT_EXCLUSION_PARTITION;
@@ -947,10 +948,10 @@ void
 estimate_rel_size(Relation rel, int32 *attr_widths,
 				  BlockNumber *pages, double *tuples, double *allvisfrac)
 {
-	BlockNumber curpages;
-	BlockNumber relpages;
-	double		reltuples;
-	BlockNumber relallvisible;
+	BlockNumber curpages = 0;
+	BlockNumber relpages = 0;
+	double		reltuples = 0;
+	BlockNumber relallvisible = 0;
 	double		density;
 
 	switch (rel->rd_rel->relkind)
@@ -964,6 +965,21 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 
 		case RELKIND_INDEX:
 
+			/* global temp table get relstats from localhash */
+			if (RELATION_IS_GLOBAL_TEMP(rel))
+			{
+				get_gtt_relstats(RelationGetRelid(rel),
+								&relpages, &reltuples, &relallvisible,
+								NULL, NULL);
+			}
+			else
+			{
+				/* coerce values in pg_class to more desirable types */
+				relpages = (BlockNumber) rel->rd_rel->relpages;
+				reltuples = (double) rel->rd_rel->reltuples;
+				relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
+			}
+
 			/*
 			 * XXX: It'd probably be good to move this into a callback,
 			 * individual index types e.g. know if they have a metapage.
@@ -972,11 +988,6 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 			/* it has storage, ok to call the smgr */
 			curpages = RelationGetNumberOfBlocks(rel);
 
-			/* coerce values in pg_class to more desirable types */
-			relpages = (BlockNumber) rel->rd_rel->relpages;
-			reltuples = (double) rel->rd_rel->reltuples;
-			relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
-
 			/* report estimated # pages */
 			*pages = curpages;
 			/* quick exit if rel is clearly empty */
@@ -986,10 +997,6 @@ estimate_rel_size(Relation rel, int32 *attr_widths,
 				*allvisfrac = 0;
 				break;
 			}
-			/* coerce values in pg_class to more desirable types */
-			relpages = (BlockNumber) rel->rd_rel->relpages;
-			reltuples = (double) rel->rd_rel->reltuples;
-			relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
 
 			/*
 			 * Discount the metapage while estimating the number of tuples.
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
index 85d7a96..67b200d 100644
--- a/src/backend/parser/analyze.c
+++ b/src/backend/parser/analyze.c
@@ -2579,6 +2579,11 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					 errmsg("materialized views must not use temporary tables or views")));
 
+		if (is_query_using_gtt(query))
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("materialized views must not use global temporary tables or views")));
+
 		/*
 		 * A materialized view would either need to save parameters for use in
 		 * maintaining/loading the data or prohibit them entirely.  The latter
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 3f67aaf..333e54f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -3268,17 +3268,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; }
@@ -11505,19 +11499,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 4dd8150..637a15c 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -59,6 +59,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);
 
 
 /*
@@ -3420,3 +3421,49 @@ isQueryUsingTempRelation_walker(Node *node, void *context)
 								  isQueryUsingTempRelation_walker,
 								  context);
 }
+
+/* check if the query uses global temp table */
+static bool
+is_query_using_gtt_walker(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Query))
+	{
+		Query	   *query = (Query *) node;
+		ListCell   *rtable;
+
+		foreach(rtable, query->rtable)
+		{
+			RangeTblEntry *rte = lfirst(rtable);
+
+			if (rte->rtekind == RTE_RELATION)
+			{
+				Relation	rel = relation_open(rte->relid, AccessShareLock);
+				char		relpersistence = rel->rd_rel->relpersistence;
+
+				relation_close(rel, AccessShareLock);
+				if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+					return true;
+			}
+		}
+
+		return query_tree_walker(query,
+								 is_query_using_gtt_walker,
+								 context,
+								 QTW_IGNORE_JOINALIASES);
+	}
+
+	return expression_tree_walker(node,
+								  is_query_using_gtt_walker,
+								  context);
+}
+
+/* check if the query uses global temp table */
+bool
+is_query_using_gtt(Query *query)
+{
+	return is_query_using_gtt_walker((Node *) query, NULL);
+}
+
diff --git a/src/backend/parser/parse_utilcmd.c b/src/backend/parser/parse_utilcmd.c
index ee47547..d13e253 100644
--- a/src/backend/parser/parse_utilcmd.c
+++ b/src/backend/parser/parse_utilcmd.c
@@ -359,6 +359,13 @@ generateSerialExtraStmts(CreateStmtContext *cxt, ColumnDef *column,
 	AlterSeqStmt *altseqstmt;
 	List	   *attnamelist;
 
+	if (cxt->relation->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		ereport(ERROR,
+			(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+			 errmsg("Global temp table does not yet support serial column")));
+	}
+
 	/*
 	 * Determine namespace and name to use for the sequence.
 	 *
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index c1dd816..f2d3df9 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -2091,6 +2091,11 @@ do_autovacuum(void)
 			}
 			continue;
 		}
+		else if (classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			/* autovacuum skip vacuum global temp table */
+			continue;
+		}
 
 		/* Fetch reloptions and the pgstat entry for this table */
 		relopts = extract_autovac_opts(tuple, pg_class_desc);
@@ -2157,7 +2162,9 @@ do_autovacuum(void)
 		/*
 		 * We cannot safely process other backends' temp tables, so skip 'em.
 		 */
-		if (classForm->relpersistence == RELPERSISTENCE_TEMP)
+		/* autovacuum skip vacuum global temp table */
+		if (classForm->relpersistence == RELPERSISTENCE_TEMP || 
+			classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
 			continue;
 
 		relid = classForm->oid;
diff --git a/src/backend/storage/buffer/bufmgr.c b/src/backend/storage/buffer/bufmgr.c
index 7ad1073..8e81701 100644
--- a/src/backend/storage/buffer/bufmgr.c
+++ b/src/backend/storage/buffer/bufmgr.c
@@ -53,6 +53,8 @@
 #include "utils/resowner_private.h"
 #include "utils/timestamp.h"
 
+#include "utils/guc.h"
+#include "catalog/storage_gtt.h"
 
 /* Note: these two macros only work on shared buffers, not local ones! */
 #define BufHdrGetBlock(bufHdr)	((Block) (BufferBlocks + ((Size) (bufHdr)->buf_id) * BLCKSZ))
@@ -432,7 +434,7 @@ ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref)
 static Buffer ReadBuffer_common(SMgrRelation reln, char relpersistence,
 								ForkNumber forkNum, BlockNumber blockNum,
 								ReadBufferMode mode, BufferAccessStrategy strategy,
-								bool *hit);
+								bool *hit, Relation rel);
 static bool PinBuffer(BufferDesc *buf, BufferAccessStrategy strategy);
 static void PinBuffer_Locked(BufferDesc *buf);
 static void UnpinBuffer(BufferDesc *buf, bool fixOwner);
@@ -664,7 +666,8 @@ ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum,
 	 */
 	pgstat_count_buffer_read(reln);
 	buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence,
-							forkNum, blockNum, mode, strategy, &hit);
+							forkNum, blockNum, mode, strategy, &hit,
+							reln);
 	if (hit)
 		pgstat_count_buffer_hit(reln);
 	return buf;
@@ -692,7 +695,7 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 	Assert(InRecovery);
 
 	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum,
-							 mode, strategy, &hit);
+							 mode, strategy, &hit, NULL);
 }
 
 
@@ -704,7 +707,8 @@ ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum,
 static Buffer
 ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 				  BlockNumber blockNum, ReadBufferMode mode,
-				  BufferAccessStrategy strategy, bool *hit)
+				  BufferAccessStrategy strategy, bool *hit,
+				  Relation rel)
 {
 	BufferDesc *bufHdr;
 	Block		bufBlock;
@@ -719,6 +723,15 @@ ReadBuffer_common(SMgrRelation smgr, char relpersistence, ForkNumber forkNum,
 
 	isExtend = (blockNum == P_NEW);
 
+	/* create storage when first read data page for gtt */
+	if (relpersistence == RELPERSISTENCE_GLOBAL_TEMP &&
+		(isExtend || blockNum == 0) &&
+		forkNum == MAIN_FORKNUM &&
+		!gtt_storage_attached(smgr->smgr_rnode.node.relNode))
+	{
+		RelationCreateStorage(smgr->smgr_rnode.node, relpersistence, rel);
+	}
+
 	TRACE_POSTGRESQL_BUFFER_READ_START(forkNum, blockNum,
 									   smgr->smgr_rnode.node.spcNode,
 									   smgr->smgr_rnode.node.dbNode,
@@ -2799,6 +2812,16 @@ FlushBuffer(BufferDesc *buf, SMgrRelation reln)
 BlockNumber
 RelationGetNumberOfBlocksInFork(Relation relation, ForkNumber forkNum)
 {
+	/*
+	 * When this backend not init gtt storage
+	 * return 0
+	 */
+	if (RELATION_IS_GLOBAL_TEMP(relation) &&
+		!gtt_storage_attached(relation->rd_node.relNode))
+	{
+		return 0;
+	}
+
 	switch (relation->rd_rel->relkind)
 	{
 		case RELKIND_SEQUENCE:
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 8853706..b3544dd 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -21,6 +21,7 @@
 #include "access/nbtree.h"
 #include "access/subtrans.h"
 #include "access/twophase.h"
+#include "catalog/storage_gtt.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "pgstat.h"
@@ -147,6 +148,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, BTreeShmemSize());
 		size = add_size(size, SyncScanShmemSize());
 		size = add_size(size, AsyncShmemSize());
+		size = add_size(size, active_gtt_shared_hash_size());
 #ifdef EXEC_BACKEND
 		size = add_size(size, ShmemBackendArraySize());
 #endif
@@ -217,6 +219,8 @@ CreateSharedMemoryAndSemaphores(void)
 	SUBTRANSShmemInit();
 	MultiXactShmemInit();
 	InitBufferPool();
+	/* global temporary table */
+	active_gtt_shared_hash_init();
 
 	/*
 	 * Set up lock manager
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3da5307..21bd1f2 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -60,6 +60,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))))
 
@@ -3973,3 +3974,77 @@ KnownAssignedXidsReset(void)
 
 	LWLockRelease(ProcArrayLock);
 }
+
+/*
+ * search all active backend to get oldest frozenxid
+ * for global temp table.
+ */
+int
+list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n)
+{
+	ProcArrayStruct *arrayP = procArray;
+	TransactionId result = InvalidTransactionId;
+	int			index;
+	int			flags = 0;
+	int			i = 0;
+
+	if (max_active_gtt <= 0)
+		return 0;
+
+	if (max_size > 0)
+	{
+		Assert(pids);
+		Assert(xids);
+		Assert(n);
+		*n = 0;
+	}
+
+	if (max_active_gtt <= 0)
+		return InvalidTransactionId;
+
+	if (RecoveryInProgress())
+		return InvalidTransactionId;
+
+	flags |= PROC_IS_AUTOVACUUM;
+	flags |= PROC_IN_LOGICAL_DECODING;
+
+	LWLockAcquire(ProcArrayLock, LW_SHARED);
+	if (max_size > 0 && max_size < arrayP->numProcs)
+	{
+		LWLockRelease(ProcArrayLock);
+		elog(ERROR, "list_all_gtt_frozenxids require more array");
+	}
+
+	for (index = 0; index < arrayP->numProcs; index++)
+	{
+		int			pgprocno = arrayP->pgprocnos[index];
+		volatile PGPROC *proc = &allProcs[pgprocno];
+		volatile PGXACT *pgxact = &allPgXact[pgprocno];
+
+		if (pgxact->vacuumFlags & flags)
+			continue;
+
+		if (proc->databaseId == MyDatabaseId &&
+			TransactionIdIsNormal(proc->session_gtt_frozenxid))
+		{
+			if (result == InvalidTransactionId)
+				result = proc->session_gtt_frozenxid;
+			else if (TransactionIdPrecedes(proc->session_gtt_frozenxid, result))
+				result = proc->session_gtt_frozenxid;
+
+			if (max_size > 0)
+			{
+				pids[i] = proc->pid;
+				xids[i] = proc->session_gtt_frozenxid;
+				i++;
+			}
+		}
+	}
+	LWLockRelease(ProcArrayLock);
+
+	if (max_size > 0)
+		*n = i;
+
+	return result;
+}
+
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index b3c54a6..645456c 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -396,6 +396,7 @@ InitProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
@@ -578,6 +579,7 @@ InitAuxiliaryProcess(void)
 	MyProc->databaseId = InvalidOid;
 	MyProc->roleId = InvalidOid;
 	MyProc->tempNamespaceId = InvalidOid;
+	MyProc->session_gtt_frozenxid = InvalidTransactionId;	/* init session level gtt frozenxid */
 	MyProc->isBackgroundWorker = IsBackgroundWorker;
 	MyPgXact->delayChkpt = false;
 	MyPgXact->vacuumFlags = 0;
diff --git a/src/backend/storage/smgr/md.c b/src/backend/storage/smgr/md.c
index 07f3c93..cee8f9e 100644
--- a/src/backend/storage/smgr/md.c
+++ b/src/backend/storage/smgr/md.c
@@ -654,6 +654,12 @@ mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum,
 		 */
 		if (zero_damaged_pages || InRecovery)
 			MemSet(buffer, 0, BLCKSZ);
+		else if(SmgrIsTemp(reln) && blocknum == 0 && forknum == MAIN_FORKNUM)
+		{
+			/* global temp table init btree meta page */
+			MemSet(buffer, 0, BLCKSZ);
+			mdwrite(reln, forknum, blocknum, buffer, true);
+		}
 		else
 			ereport(ERROR,
 					(errcode(ERRCODE_DATA_CORRUPTED),
diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c
index a87e721..adce760 100644
--- a/src/backend/utils/adt/dbsize.c
+++ b/src/backend/utils/adt/dbsize.c
@@ -1008,6 +1008,10 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
 				Assert(backend != InvalidBackendId);
 			}
 			break;
+		/* For global temp table */
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			backend = BackendIdForTempRelations();
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relform->relpersistence);
 			backend = InvalidBackendId; /* placate compiler */
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 35a8995..42dc3a5 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -141,6 +141,7 @@
 #include "utils/timestamp.h"
 #include "utils/typcache.h"
 
+#include "catalog/storage_gtt.h"
 
 /* Hooks for plugins to get control when we ask for stats */
 get_relation_stats_hook_type get_relation_stats_hook = NULL;
@@ -4568,12 +4569,25 @@ examine_variable(PlannerInfo *root, Node *node, int varRelid,
 						}
 						else if (index->indpred == NIL)
 						{
-							vardata->statsTuple =
-								SearchSysCache3(STATRELATTINH,
-												ObjectIdGetDatum(index->indexoid),
-												Int16GetDatum(pos + 1),
-												BoolGetDatum(false));
-							vardata->freefunc = ReleaseSysCache;
+							char rel_persistence = get_rel_persistence(index->indexoid);
+
+							if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+							{
+								vardata->statsTuple =
+									get_gtt_att_statistic(index->indexoid,
+														Int16GetDatum(pos + 1),
+														false);
+								vardata->freefunc = release_gtt_statistic_cache;
+							}
+							else
+							{
+								vardata->statsTuple =
+									SearchSysCache3(STATRELATTINH,
+													ObjectIdGetDatum(index->indexoid),
+													Int16GetDatum(pos + 1),
+													BoolGetDatum(false));
+								vardata->freefunc = ReleaseSysCache;
+							}
 
 							if (HeapTupleIsValid(vardata->statsTuple))
 							{
@@ -4652,15 +4666,27 @@ examine_simple_variable(PlannerInfo *root, Var *var,
 	}
 	else if (rte->rtekind == RTE_RELATION)
 	{
-		/*
-		 * Plain table or parent of an inheritance appendrel, so look up the
-		 * column in pg_statistic
-		 */
-		vardata->statsTuple = SearchSysCache3(STATRELATTINH,
-											  ObjectIdGetDatum(rte->relid),
-											  Int16GetDatum(var->varattno),
-											  BoolGetDatum(rte->inh));
-		vardata->freefunc = ReleaseSysCache;
+		char rel_persistence = get_rel_persistence(rte->relid);
+
+		if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata->statsTuple = get_gtt_att_statistic(rte->relid,
+														var->varattno,
+														rte->inh);
+			vardata->freefunc = release_gtt_statistic_cache;
+		}
+		else
+		{
+			/*
+			 * Plain table or parent of an inheritance appendrel, so look up the
+			 * column in pg_statistic
+			 */
+			vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+												  ObjectIdGetDatum(rte->relid),
+												  Int16GetDatum(var->varattno),
+												  BoolGetDatum(rte->inh));
+			vardata->freefunc = ReleaseSysCache;
+		}
 
 		if (HeapTupleIsValid(vardata->statsTuple))
 		{
@@ -5972,6 +5998,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;
@@ -5989,6 +6016,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													rte->inh);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6000,6 +6034,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;
@@ -6015,6 +6051,13 @@ btcostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 				!vardata.freefunc)
 				elog(ERROR, "no function provided to release variable stats with");
 		}
+		else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+		{
+			vardata.statsTuple = get_gtt_att_statistic(relid,
+													colnum,
+													false);
+			vardata.freefunc = release_gtt_statistic_cache;
+		}
 		else
 		{
 			vardata.statsTuple = SearchSysCache3(STATRELATTINH,
@@ -6881,6 +6924,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))
@@ -6893,6 +6938,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					elog(ERROR,
 						 "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(rte->relid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple =
@@ -6905,6 +6958,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.
@@ -6924,6 +6979,14 @@ brincostestimate(PlannerInfo *root, IndexPath *path, double loop_count,
 					!vardata.freefunc)
 					elog(ERROR, "no function provided to release variable stats with");
 			}
+			else if (rel_persistence == RELPERSISTENCE_GLOBAL_TEMP)
+			{
+				vardata.statsTuple =
+					get_gtt_att_statistic(index->indexoid,
+										attnum,
+										false);
+				vardata.freefunc = release_gtt_statistic_cache;
+			}
 			else
 			{
 				vardata.statsTuple = SearchSysCache3(STATRELATTINH,
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 27602fa..25a411a 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -34,6 +34,7 @@
 #include "catalog/pg_statistic.h"
 #include "catalog/pg_transform.h"
 #include "catalog/pg_type.h"
+#include "catalog/storage_gtt.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "utils/array.h"
@@ -46,6 +47,7 @@
 #include "utils/syscache.h"
 #include "utils/typcache.h"
 
+
 /* Hook for plugins to get control in get_attavgwidth() */
 get_attavgwidth_hook_type get_attavgwidth_hook = NULL;
 
@@ -2878,6 +2880,18 @@ get_attavgwidth(Oid relid, AttrNumber attnum)
 		if (stawidth > 0)
 			return stawidth;
 	}
+	if (get_rel_persistence(relid) == RELPERSISTENCE_GLOBAL_TEMP)
+	{
+		tp = get_gtt_att_statistic(relid, attnum, false);
+		if (!HeapTupleIsValid(tp))
+			return 0;
+
+		stawidth = ((Form_pg_statistic) GETSTRUCT(tp))->stawidth;
+		if (stawidth > 0)
+			return stawidth;
+		else
+			return 0;
+	}
 	tp = SearchSysCache3(STATRELATTINH,
 						 ObjectIdGetDatum(relid),
 						 Int16GetDatum(attnum),
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 585dcee..b8f2a41 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -1130,6 +1130,16 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
 				relation->rd_islocaltemp = false;
 			}
 			break;
+		case RELPERSISTENCE_GLOBAL_TEMP:
+			{
+				relation->rd_backend = BackendIdForTempRelations();
+				/*
+				 * For global temp table, all backend can use
+				 * this relation, so rd_islocaltemp always true.
+				 */
+				relation->rd_islocaltemp = true;
+			}
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c",
 				 relation->rd_rel->relpersistence);
@@ -3311,6 +3321,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 = true;
+			break;
 		default:
 			elog(ERROR, "invalid relpersistence: %c", relpersistence);
 			break;
@@ -3425,6 +3439,9 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 	TransactionId freezeXid = InvalidTransactionId;
 	RelFileNode newrnode;
 
+	if (RELATION_IS_GLOBAL_TEMP(relation))
+		elog(ERROR, "global temp table does not allow setting new relfilenode");
+
 	/* Allocate a new relfilenode */
 	newrelfilenode = GetNewRelFileNode(relation->rd_rel->reltablespace, NULL,
 									   persistence);
@@ -3465,7 +3482,7 @@ RelationSetNewRelfilenode(Relation relation, char persistence)
 				/* handle these directly, at least for now */
 				SMgrRelation srel;
 
-				srel = RelationCreateStorage(newrnode, persistence);
+				srel = RelationCreateStorage(newrnode, persistence, relation);
 				smgrclose(srel);
 			}
 			break;
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 31a5ef0..518d0fa 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -138,6 +138,18 @@ char	   *GUC_check_errmsg_string;
 char	   *GUC_check_errdetail_string;
 char	   *GUC_check_errhint_string;
 
+/*
+ * num = 0 means disable global temp table feature.
+ * global temp table define can still storage in catalog
+ * just can not use.
+ * num > 0 means database can management num active global temp table.
+ */
+#define	MIN_NUM_ACTIVE_GTT			0
+#define	DEFAULT_NUM_ACTIVE_GTT			1000
+#define	MAX_NUM_ACTIVE_GTT			1000000
+
+int		max_active_gtt = MIN_NUM_ACTIVE_GTT;
+
 static void do_serialize(char **destptr, Size *maxbytes, const char *fmt,...) pg_attribute_printf(3, 4);
 
 static void set_config_sourcefile(const char *name, char *sourcefile,
@@ -1962,6 +1974,15 @@ static struct config_bool ConfigureNamesBool[] =
 static struct config_int ConfigureNamesInt[] =
 {
 	{
+		{"max_active_global_temporary_table", PGC_POSTMASTER, UNGROUPED,
+			gettext_noop("max active global temporary table."),
+			NULL
+		},
+		&max_active_gtt,
+		DEFAULT_NUM_ACTIVE_GTT, MIN_NUM_ACTIVE_GTT, MAX_NUM_ACTIVE_GTT,
+		NULL, NULL, NULL
+	},
+	{
 		{"archive_timeout", PGC_SIGHUP, WAL_ARCHIVING,
 			gettext_noop("Forces a switch to the next WAL file if a "
 						 "new file has not been started within N seconds."),
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index bf69adc..72e291b 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -15585,6 +15585,7 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 	{
 		char	   *ftoptions = NULL;
 		char	   *srvname = NULL;
+		char		*table_type = NULL;
 
 		switch (tbinfo->relkind)
 		{
@@ -15636,9 +15637,15 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 tbinfo->dobj.catId.oid, false);
 
+		if (tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED)
+			table_type = "UNLOGGED ";
+		else if (tbinfo->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+			table_type = "GLOBAL TEMPORARY ";
+		else
+			table_type = "";
+
 		appendPQExpBuffer(q, "CREATE %s%s %s",
-						  tbinfo->relpersistence == RELPERSISTENCE_UNLOGGED ?
-						  "UNLOGGED " : "",
+						  table_type,
 						  reltypename,
 						  qualrelname);
 
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index 090b6ba..34b4683 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -165,6 +165,7 @@ typedef FormData_pg_class *Form_pg_class;
 #define		  RELPERSISTENCE_PERMANENT	'p' /* regular table */
 #define		  RELPERSISTENCE_UNLOGGED	'u' /* unlogged permanent table */
 #define		  RELPERSISTENCE_TEMP		't' /* temporary table */
+#define		  RELPERSISTENCE_GLOBAL_TEMP	'g' /* global temporary table */
 
 /* default selection for replica identity (primary key or nothing) */
 #define		  REPLICA_IDENTITY_DEFAULT	'd'
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index 3579d3f..2bde386 100644
--- a/src/include/catalog/storage.h
+++ b/src/include/catalog/storage.h
@@ -19,7 +19,7 @@
 #include "storage/smgr.h"
 #include "utils/relcache.h"
 
-extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence);
+extern SMgrRelation RelationCreateStorage(RelFileNode rnode, char relpersistence, Relation rel);
 extern void RelationDropStorage(Relation rel);
 extern void RelationPreserveStorage(RelFileNode rnode, bool atCommit);
 extern void RelationTruncate(Relation rel, BlockNumber nblocks);
diff --git a/src/include/catalog/storage_gtt.h b/src/include/catalog/storage_gtt.h
new file mode 100644
index 0000000..ea41e66
--- /dev/null
+++ b/src/include/catalog/storage_gtt.h
@@ -0,0 +1,39 @@
+/*-------------------------------------------------------------------------
+ *
+ * storage_gtt.h
+ *	  prototypes for functions in backend/catalog/storage_gtt.c
+ *
+ * src/include/catalog/storage_gtt.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STORAGE_GTT_H
+#define STORAGE_GTT_H
+
+#include "access/htup.h"
+#include "storage/block.h"
+#include "storage/relfilenode.h"
+#include "utils/relcache.h"
+
+extern Size active_gtt_shared_hash_size(void);
+extern void active_gtt_shared_hash_init(void);
+extern void remember_gtt_storage_info(RelFileNode rnode, Relation rel);
+extern void forget_gtt_storage_info(Oid relid);
+extern bool is_other_backend_use_gtt(RelFileNode node);
+extern bool gtt_storage_attached(Oid relid);
+extern Bitmapset *copy_active_gtt_bitmap(RelFileNode node);
+extern void up_gtt_att_statistic(Oid reloid, int attnum, bool inh, int natts,
+								TupleDesc tupleDescriptor, Datum *values, bool *isnull);
+extern HeapTuple get_gtt_att_statistic(Oid reloid, int attnum, bool inh);
+extern void release_gtt_statistic_cache(HeapTuple tup);
+extern void up_gtt_relstats(Relation relation,
+							BlockNumber num_pages,
+							double num_tuples,
+							BlockNumber num_all_visible_pages,
+							TransactionId relfrozenxid,
+							TransactionId relminmxid);
+extern void get_gtt_relstats(Oid relid, BlockNumber *relpages, double *reltuples,
+							BlockNumber *relallvisible, TransactionId *relfrozenxid,
+							TransactionId *relminmxid);
+
+#endif							/* STORAGE_H */
diff --git a/src/include/parser/parse_relation.h b/src/include/parser/parse_relation.h
index f7e0781..4f5a353 100644
--- a/src/include/parser/parse_relation.h
+++ b/src/include/parser/parse_relation.h
@@ -130,4 +130,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/lwlock.h b/src/include/storage/lwlock.h
index f627dfe..cfc6c78 100644
--- a/src/include/storage/lwlock.h
+++ b/src/include/storage/lwlock.h
@@ -220,6 +220,7 @@ typedef enum BuiltinTrancheIds
 	LWTRANCHE_TBM,
 	LWTRANCHE_PARALLEL_APPEND,
 	LWTRANCHE_SXACT,
+	LWTRANCHE_GTT_CTL,
 	LWTRANCHE_FIRST_USER_DEFINED
 }			BuiltinTrancheIds;
 
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 281e1db..2ab8e91 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -117,6 +117,8 @@ struct PGPROC
 	Oid			tempNamespaceId;	/* OID of temp schema this backend is
 									 * using */
 
+	TransactionId session_gtt_frozenxid;	/* session level global temp table relfrozenxid */
+
 	bool		isBackgroundWorker; /* true if background worker. */
 
 	/*
diff --git a/src/include/storage/procarray.h b/src/include/storage/procarray.h
index da8b672..8e7d4c7 100644
--- a/src/include/storage/procarray.h
+++ b/src/include/storage/procarray.h
@@ -124,4 +124,6 @@ extern void ProcArraySetReplicationSlotXmin(TransactionId xmin,
 extern void ProcArrayGetReplicationSlotXmin(TransactionId *xmin,
 											TransactionId *catalog_xmin);
 
+extern int list_all_session_gtt_frozenxids(int max_size, int *pids, uint32 *xids, int *n);
+
 #endif							/* PROCARRAY_H */
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 6791e0c..82b672f 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -276,6 +276,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 8b8b237..c1962b7 100644
--- a/src/include/utils/rel.h
+++ b/src/include/utils/rel.h
@@ -272,6 +272,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
@@ -530,7 +531,8 @@ 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
@@ -609,6 +611,17 @@ typedef struct ViewOptions
  */
 #define RelationGetPartitionDesc(relation) ((relation)->rd_partdesc)
 
+/* global temp table implementations */
+#define RELATION_IS_GLOBAL_TEMP(relation)	((relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP)
+
+#define RELATION_GTT_ON_COMMIT_DELETE(relation)	\
+	((relation)->rd_options && \
+	 (relation)->rd_rel->relkind == RELKIND_RELATION && \
+	 (relation)->rd_rel->relpersistence == RELPERSISTENCE_GLOBAL_TEMP ? \
+	 ((StdRdOptions *) (relation)->rd_options)->on_commit_delete_rows : false)
+
+#define RelationGetRelPersistence(relation) ((relation)->rd_rel->relpersistence)
+
 /* routines in utils/cache/relcache.c */
 extern void RelationIncrementReferenceCount(Relation rel);
 extern void RelationDecrementReferenceCount(Relation rel);
diff --git a/src/test/regress/expected/gtt_clean.out b/src/test/regress/expected/gtt_clean.out
new file mode 100644
index 0000000..50ca9ac
--- /dev/null
+++ b/src/test/regress/expected/gtt_clean.out
@@ -0,0 +1,7 @@
+reset search_path;
+drop schema gtt cascade;
+NOTICE:  drop cascades to 4 other objects
+DETAIL:  drop cascades to table gtt.gtt1
+drop cascades to table gtt.gtt2
+drop cascades to table gtt.gtt3
+drop cascades to table gtt.gtt_t_kenyon
diff --git a/src/test/regress/expected/gtt_error.out b/src/test/regress/expected/gtt_error.out
new file mode 100644
index 0000000..80c16dc
--- /dev/null
+++ b/src/test/regress/expected/gtt_error.out
@@ -0,0 +1,106 @@
+CREATE SCHEMA IF NOT EXISTS gtt_error;
+set search_path=gtt_error,sys;
+create global temp table gtt1(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
+);
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+ERROR:  cannot create indexes on global temporary tables using concurrent mode
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+ERROR:  not support cluster global temporary tables yet
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+ERROR:  ON COMMIT can only be used on temporary tables
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+ERROR:  regular table cannot specifie on_commit_delete_rows
+-- ERROR
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+ERROR:  not support create global temporary partition table yet
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+ERROR:  not support create global temporary inheritance table yet
+-- ERROR
+create global temp table gtt3(a int primary key, b text) on commit drop;
+ERROR:  global temp table not support on commit drop clause
+-- ERROR
+create global temp table gtt4(a int primary key, b text) with(on_commit_delete_rows=true) on commit delete rows;
+ERROR:  can not defeine global temp table with on commit and with clause at same time
+-- ok
+create global temp table gtt5(a int primary key, b text) with(on_commit_delete_rows=true);
+--ERROR
+alter table gtt1 rename to gttx;
+ERROR:  not support rename global temporary tables yet
+-- ERROR
+ALTER TABLE gtt1 ADD COLUMN address varchar(30);
+ERROR:  not support alter global temporary tables yet
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  referenced relation "products" is not a global temp table
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+ERROR:  not support alter global temporary tables yet
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+ERROR:  Global temp table does not yet support serial column
+-- ERROR
+create global temp table gttx(id int GENERATED ALWAYS AS IDENTITY (START WITH 2));
+ERROR:  Global temp table does not yet support serial column
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+ERROR:  materialized views must not use global temporary tables or views
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+ERROR:  only support btree index on global temp table
+create index idx_err on gtt1 using gist (a);
+ERROR:  data type integer has no default operator class for access method "gist"
+HINT:  You must specify an operator class for the index or define a default operator class for the data type.
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+ERROR:  only support btree index on global temp table
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+ERROR:  only support btree index on global temp table
+reset search_path;
+drop schema gtt_error cascade;
+NOTICE:  drop cascades to 8 other objects
+DETAIL:  drop cascades to table gtt_error.gtt1
+drop cascades to table gtt_error.gtt2
+drop cascades to table gtt_error.gtt3
+drop cascades to table gtt_error.tmp_t0
+drop cascades to table gtt_error.tbl_inherits_parent
+drop cascades to table gtt_error.tbl_inherits_parent_global_temp
+drop cascades to table gtt_error.products
+drop cascades to table gtt_error.gtt5
diff --git a/src/test/regress/expected/gtt_parallel_1.out b/src/test/regress/expected/gtt_parallel_1.out
new file mode 100644
index 0000000..30d8a7b
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_1.out
@@ -0,0 +1,84 @@
+set search_path=gtt,sys;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+ a | b 
+---+---
+(0 rows)
+
+reset search_path;
diff --git a/src/test/regress/expected/gtt_parallel_2.out b/src/test/regress/expected/gtt_parallel_2.out
new file mode 100644
index 0000000..70bf4a6
--- /dev/null
+++ b/src/test/regress/expected/gtt_parallel_2.out
@@ -0,0 +1,142 @@
+set search_path=gtt,sys;
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+ 3 | test1
+(3 rows)
+
+rollback;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test1
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+(1 row)
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 1 | test1
+ 2 | test2
+(2 rows)
+
+truncate gtt3;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 3 | test3
+(1 row)
+
+rollback;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+begin;
+select * from gtt3 order by a;
+ a | b 
+---+---
+(0 rows)
+
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 5 | test5
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+ a |   b   
+---+-------
+ 6 | test6
+(1 row)
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+ count  
+--------
+ 100000
+(1 row)
+
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+             QUERY PLAN             
+------------------------------------
+ Index Scan using gtt3_pkey on gtt3
+   Index Cond: (a = 300)
+(2 rows)
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',10000);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid) from pg_class where relname = 'gtt_t_kenyon';
+   relname    | pg_relation_size | pg_relation_size 
+--------------+------------------+------------------
+ gtt_t_kenyon |           450560 |            16384
+(1 row)
+
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+   relname    
+--------------
+ gtt_t_kenyon
+(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..9e8f5f0
--- /dev/null
+++ b/src/test/regress/expected/gtt_prepare.out
@@ -0,0 +1,7 @@
+CREATE SCHEMA IF NOT EXISTS gtt;
+set search_path=gtt,sys;
+create global temp table gtt1(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 gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+reset search_path;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index fc0f141..6836eeb 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -121,3 +121,9 @@ test: fast_default
 
 # run stats by itself because its delay may be insufficient under heavy load
 test: stats
+
+# global temp table test
+test: gtt_error
+test: gtt_prepare
+test: gtt_parallel_1 gtt_parallel_2
+test: gtt_clean
diff --git a/src/test/regress/sql/gtt_error.sql b/src/test/regress/sql/gtt_error.sql
new file mode 100644
index 0000000..e895574
--- /dev/null
+++ b/src/test/regress/sql/gtt_error.sql
@@ -0,0 +1,106 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt_error;
+
+set search_path=gtt_error,sys;
+
+create global temp table gtt1(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
+);
+
+CREATE global temp TABLE products (
+    product_no integer PRIMARY KEY,
+    name text,
+    price numeric
+);
+
+-- ERROR
+create index CONCURRENTLY idx_gtt1 on gtt1 (b);
+
+-- ERROR
+cluster gtt1 using gtt1_pkey;
+
+-- ERROR
+create table gtt1(a int primary key, b text) on commit delete rows;
+
+-- ERROR
+create table gtt1(a int primary key, b text) with(on_commit_delete_rows=true);
+
+-- ERROR
+CREATE global temp TABLE measurement (
+    logdate         date not null,
+    peaktemp        int,
+    unitsales       int
+) PARTITION BY RANGE (logdate);
+
+-- ERROR
+create global temp table tbl_inherits_partition() inherits (tbl_inherits_parent);
+
+-- 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);
+
+--ERROR
+alter table gtt1 rename to gttx;
+
+-- ERROR
+ALTER TABLE gtt1 ADD COLUMN address varchar(30);
+
+-- ERROR
+CREATE TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE global temp TABLE orders (
+    order_id integer PRIMARY KEY,
+    product_no integer REFERENCES products (product_no),
+    quantity integer
+);
+
+-- ERROR
+CREATE GLOBAL TEMPORARY TABLE mytable (
+  id SERIAL PRIMARY KEY,
+  data text
+) on commit preserve rows;
+
+-- ERROR
+create global temp table gttx(id int GENERATED ALWAYS AS IDENTITY (START WITH 2));
+
+--ERROR
+CREATE MATERIALIZED VIEW mv_gtt1 as select * from gtt1;
+
+-- ALL ERROR
+create index idx_err on gtt1 using hash (a);
+create index idx_err on gtt1 using gist (a);
+create index idx_tmp_t0_1 on tmp_t0 using gin (c0);
+create index idx_tmp_t0_1 on tmp_t0 using gist (c0);
+
+reset search_path;
+
+drop schema gtt_error cascade;
+
diff --git a/src/test/regress/sql/gtt_parallel_1.sql b/src/test/regress/sql/gtt_parallel_1.sql
new file mode 100644
index 0000000..d7d81de
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_1.sql
@@ -0,0 +1,42 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+commit;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+rollback;
+select * from gtt1 order by a;
+
+truncate gtt1;
+select * from gtt1 order by a;
+
+begin;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+select * from gtt1 order by a;
+insert into gtt1 values(1, 'test1');
+rollback;
+select * from gtt1 order by a;
+
+begin;
+select * from gtt1 order by a;
+truncate gtt1;
+insert into gtt1 values(1, 'test1');
+select * from gtt1 order by a;
+truncate gtt1;
+commit;
+select * from gtt1 order by a;
+
+reset search_path;
+
diff --git a/src/test/regress/sql/gtt_parallel_2.sql b/src/test/regress/sql/gtt_parallel_2.sql
new file mode 100644
index 0000000..cb2f7a6
--- /dev/null
+++ b/src/test/regress/sql/gtt_parallel_2.sql
@@ -0,0 +1,62 @@
+
+
+set search_path=gtt,sys;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test1');
+select * from gtt3 order by a;
+commit;
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(3, 'test1');
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+truncate gtt3;
+select * from gtt3 order by a;
+
+insert into gtt3 values(1, 'test1');
+select * from gtt3 order by a;
+
+begin;
+insert into gtt3 values(2, 'test2');
+select * from gtt3 order by a;
+truncate gtt3;
+select * from gtt3 order by a;
+insert into gtt3 values(3, 'test3');
+update gtt3 set a = 3 where b = 'test1';
+select * from gtt3 order by a;
+rollback;
+select * from gtt3 order by a;
+
+begin;
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(5, 'test5');
+select * from gtt3 order by a;
+truncate gtt3;
+insert into gtt3 values(6, 'test6');
+commit;
+select * from gtt3 order by a;
+
+truncate gtt3;
+insert into gtt3 values(generate_series(1,100000), 'testing');
+select count(*) from gtt3;
+analyze gtt3;
+explain (COSTS FALSE) select * from gtt3 where a =300;
+
+insert into gtt_t_kenyon select generate_series(1,2000),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God',500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',2000);
+insert into gtt_t_kenyon select generate_series(3,4),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',4000);
+insert into gtt_t_kenyon select generate_series(5,6),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',5500);
+insert into gtt_t_kenyon select generate_series(1,2),repeat('kenyon here'||'^_^',2),repeat('^_^ Kenyon is not God,Remark here!!',10000);
+select relname, pg_relation_size(oid),pg_relation_size(reltoastrelid) from pg_class where relname = 'gtt_t_kenyon';
+select relname from pg_class where relname = 'gtt_t_kenyon' and reltoastrelid != 0;
+
+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..042d9e6
--- /dev/null
+++ b/src/test/regress/sql/gtt_prepare.sql
@@ -0,0 +1,15 @@
+
+CREATE SCHEMA IF NOT EXISTS gtt;
+
+set search_path=gtt,sys;
+
+create global temp table gtt1(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 gtt_t_kenyon(id int,vname varchar(48),remark text) on commit PRESERVE rows;
+
+reset search_path;
+

Reply via email to